diff --git a/packages/contracts/src/base-entity.model.ts b/packages/contracts/src/base-entity.model.ts index 81951b07154..86f41428ca2 100644 --- a/packages/contracts/src/base-entity.model.ts +++ b/packages/contracts/src/base-entity.model.ts @@ -87,5 +87,6 @@ export enum BaseEntityEnum { OrganizationVendor = 'OrganizationVendor', Task = 'Task', TaskView = 'TaskView', + TaskLinkedIssue = 'TaskLinkedIssue', User = 'User' } diff --git a/packages/contracts/src/task-linked-issue.model.ts b/packages/contracts/src/task-linked-issue.model.ts index a7b0f71d6dc..0cac874aaa8 100644 --- a/packages/contracts/src/task-linked-issue.model.ts +++ b/packages/contracts/src/task-linked-issue.model.ts @@ -1,4 +1,4 @@ -import { IBasePerTenantAndOrganizationEntityModel } from './base-entity.model'; +import { IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model'; import { ITask } from './task.model'; export enum TaskRelatedIssuesRelationEnum { @@ -8,23 +8,21 @@ export enum TaskRelatedIssuesRelationEnum { CLONES = 4, IS_DUPLICATED_BY = 5, DUPLICATES = 6, - RELATES_TO = 7, + RELATES_TO = 7 } -export interface ITaskLinkedIssue - extends IBasePerTenantAndOrganizationEntityModel { +export interface ITaskLinkedIssue extends IBasePerTenantAndOrganizationEntityModel { action: TaskRelatedIssuesRelationEnum; taskFrom?: ITask; - taskFromId: ITask['id']; + taskFromId: ID; taskTo?: ITask; - taskToId: ITask['id']; + taskToId: ID; } export interface ITaskLinkedIssueCreateInput extends ITaskLinkedIssue {} -export interface ITaskLinkedIssueUpdateInput - extends Partial { - id?: string; +export interface ITaskLinkedIssueUpdateInput extends Partial { + id?: ID; } export interface ILinkedIssueFindInput diff --git a/packages/core/src/activity-log/activity-log.service.ts b/packages/core/src/activity-log/activity-log.service.ts index fa18e7334ac..1019cc68d37 100644 --- a/packages/core/src/activity-log/activity-log.service.ts +++ b/packages/core/src/activity-log/activity-log.service.ts @@ -122,7 +122,7 @@ export class ActivityLogService extends TenantAwareCrudService { /** * @description Create or Update Activity Log * @template T - * @param {BaseEntityEnum} entityType - Entity type for whom creating activity log (E.g : Task, OrganizationProject, etc.) + * @param {BaseEntityEnum} entity - Entity type for whom creating activity log (E.g : Task, OrganizationProject, etc.) * @param {string} entityName - Name or Title of the entity * @param {ActorTypeEnum} actor - The actor type performing the action (User or System) * @param {ID} organizationId diff --git a/packages/core/src/tasks/linked-issue/commands/handlers/index.ts b/packages/core/src/tasks/linked-issue/commands/handlers/index.ts new file mode 100644 index 00000000000..d78cf9b943f --- /dev/null +++ b/packages/core/src/tasks/linked-issue/commands/handlers/index.ts @@ -0,0 +1,4 @@ +import { TaskLinkedIssueCreateHandler } from './task-linked-issue-create.handler'; +import { TaskLinkedIssueUpdateHandler } from './task-linked-issue-update.handler'; + +export const CommandHandlers = [TaskLinkedIssueCreateHandler, TaskLinkedIssueUpdateHandler]; diff --git a/packages/core/src/tasks/linked-issue/commands/handlers/task-linked-issue-create.handler.ts b/packages/core/src/tasks/linked-issue/commands/handlers/task-linked-issue-create.handler.ts new file mode 100644 index 00000000000..a5bb065c6b1 --- /dev/null +++ b/packages/core/src/tasks/linked-issue/commands/handlers/task-linked-issue-create.handler.ts @@ -0,0 +1,15 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { ITaskLinkedIssue } from '@gauzy/contracts'; +import { TaskLinkedIssueCreateCommand } from '../task-linked-issue-create.command'; +import { TaskLinkedIssueService } from '../../task-linked-issue.service'; + +@CommandHandler(TaskLinkedIssueCreateCommand) +export class TaskLinkedIssueCreateHandler implements ICommandHandler { + constructor(private readonly taskLinkedIssueService: TaskLinkedIssueService) {} + + public async execute(command: TaskLinkedIssueCreateCommand): Promise { + const { input } = command; + + return await this.taskLinkedIssueService.create(input); + } +} diff --git a/packages/core/src/tasks/linked-issue/commands/handlers/task-linked-issue-update.handler.ts b/packages/core/src/tasks/linked-issue/commands/handlers/task-linked-issue-update.handler.ts new file mode 100644 index 00000000000..761631b5eac --- /dev/null +++ b/packages/core/src/tasks/linked-issue/commands/handlers/task-linked-issue-update.handler.ts @@ -0,0 +1,15 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { ITaskLinkedIssue } from '@gauzy/contracts'; +import { TaskLinkedIssueUpdateCommand } from '../task-linked-issue-update.command'; +import { TaskLinkedIssueService } from '../../task-linked-issue.service'; + +@CommandHandler(TaskLinkedIssueUpdateCommand) +export class TaskLinkedIssueUpdateHandler implements ICommandHandler { + constructor(private readonly taskLinkedIssueService: TaskLinkedIssueService) {} + + public async execute(command: TaskLinkedIssueUpdateCommand): Promise { + const { id, input } = command; + + return await this.taskLinkedIssueService.update(id, input); + } +} diff --git a/packages/core/src/tasks/linked-issue/commands/index.ts b/packages/core/src/tasks/linked-issue/commands/index.ts new file mode 100644 index 00000000000..67caf1505cd --- /dev/null +++ b/packages/core/src/tasks/linked-issue/commands/index.ts @@ -0,0 +1,2 @@ +export * from './task-linked-issue-create.command'; +export * from './task-linked-issue-update.command'; diff --git a/packages/core/src/tasks/linked-issue/commands/task-linked-issue-create.command.ts b/packages/core/src/tasks/linked-issue/commands/task-linked-issue-create.command.ts new file mode 100644 index 00000000000..2b02682c765 --- /dev/null +++ b/packages/core/src/tasks/linked-issue/commands/task-linked-issue-create.command.ts @@ -0,0 +1,8 @@ +import { ICommand } from '@nestjs/cqrs'; +import { ITaskLinkedIssueCreateInput } from '@gauzy/contracts'; + +export class TaskLinkedIssueCreateCommand implements ICommand { + static readonly type = '[Task Linked Issue] Create'; + + constructor(public readonly input: ITaskLinkedIssueCreateInput) {} +} diff --git a/packages/core/src/tasks/linked-issue/commands/task-linked-issue-update.command.ts b/packages/core/src/tasks/linked-issue/commands/task-linked-issue-update.command.ts new file mode 100644 index 00000000000..732cf48b4f4 --- /dev/null +++ b/packages/core/src/tasks/linked-issue/commands/task-linked-issue-update.command.ts @@ -0,0 +1,8 @@ +import { ICommand } from '@nestjs/cqrs'; +import { ID, ITaskLinkedIssueUpdateInput } from '@gauzy/contracts'; + +export class TaskLinkedIssueUpdateCommand implements ICommand { + static readonly type = '[Task Linked Issue] Update'; + + constructor(public readonly id: ID, public readonly input: ITaskLinkedIssueUpdateInput) {} +} diff --git a/packages/core/src/tasks/linked-issue/dto/task-linked-issue.dto.ts b/packages/core/src/tasks/linked-issue/dto/task-linked-issue.dto.ts index 48eb54fd381..e43b0e0262f 100644 --- a/packages/core/src/tasks/linked-issue/dto/task-linked-issue.dto.ts +++ b/packages/core/src/tasks/linked-issue/dto/task-linked-issue.dto.ts @@ -1,18 +1,8 @@ -import { TaskRelatedIssuesRelationEnum } from '@gauzy/contracts'; -import { ApiProperty } from '@nestjs/swagger'; +import { ITaskLinkedIssue } from '@gauzy/contracts'; +import { IntersectionType } from '@nestjs/swagger'; import { TenantOrganizationBaseDTO } from '../../../core/dto'; -import { IsEnum, IsUUID } from 'class-validator'; +import { TaskLinkedIssue } from '../task-linked-issue.entity'; -export class TaskLinkedIssueDTO extends TenantOrganizationBaseDTO { - @ApiProperty({ type: () => String, enum: TaskRelatedIssuesRelationEnum }) - @IsEnum(TaskRelatedIssuesRelationEnum) - action: TaskRelatedIssuesRelationEnum; - - @ApiProperty({ type: () => String }) - @IsUUID() - taskFromId: string; - - @ApiProperty({ type: () => String }) - @IsUUID() - taskToId: string; -} +export class TaskLinkedIssueDTO + extends IntersectionType(TenantOrganizationBaseDTO, TaskLinkedIssue) + implements ITaskLinkedIssue {} diff --git a/packages/core/src/tasks/linked-issue/task-linked-issue.controller.ts b/packages/core/src/tasks/linked-issue/task-linked-issue.controller.ts index ed6b90881e7..ad05dae1438 100644 --- a/packages/core/src/tasks/linked-issue/task-linked-issue.controller.ts +++ b/packages/core/src/tasks/linked-issue/task-linked-issue.controller.ts @@ -1,55 +1,162 @@ -import { Body, Controller, HttpCode, HttpStatus, Param, Post, Put, UseGuards } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { ITaskLinkedIssue, PermissionsEnum } from '@gauzy/contracts'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Param, + Post, + Put, + Query, + UseGuards +} from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { DeleteResult } from 'typeorm'; +import { ID, IPagination, ITaskLinkedIssue, PermissionsEnum } from '@gauzy/contracts'; +import { CrudController, PaginationParams } from '../../core/crud'; import { PermissionGuard, TenantPermissionGuard } from '../../shared/guards'; import { UUIDValidationPipe, UseValidationPipe } from '../../shared/pipes'; import { Permissions } from '../../shared/decorators'; -import { CrudController } from '../../core/crud'; import { TaskLinkedIssue } from './task-linked-issue.entity'; import { TaskLinkedIssueService } from './task-linked-issue.service'; import { CreateTaskLinkedIssueDTO, UpdateTaskLinkedIssueDTO } from './dto'; +import { TaskLinkedIssueCreateCommand, TaskLinkedIssueUpdateCommand } from './commands'; @ApiTags('Linked Issue') @UseGuards(TenantPermissionGuard, PermissionGuard) -@Permissions(PermissionsEnum.ALL_ORG_EDIT) +@Permissions(PermissionsEnum.ALL_ORG_EDIT, PermissionsEnum.ORG_TASK_EDIT) @Controller() export class TaskLinkedIssueController extends CrudController { - constructor(protected readonly taskLinkedIssueService: TaskLinkedIssueService) { + constructor( + private readonly taskLinkedIssueService: TaskLinkedIssueService, + private readonly commandBus: CommandBus + ) { super(taskLinkedIssueService); } /** - * Create new Linked Issue + * Finds all task linked issues based on the provided query parameters. * - * @param entity - * @returns + * @param params - The pagination and filter parameters for the query. + * @returns A promise that resolves to a paginated list of task linked issues. */ + @ApiOperation({ + summary: 'Find all' + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Found task linked issues', + type: TaskLinkedIssue + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Record not found' + }) + @Permissions(PermissionsEnum.ALL_ORG_VIEW, PermissionsEnum.ORG_TASK_VIEW) + @Get() + @UseValidationPipe() + async findAll(@Query() params: PaginationParams): Promise> { + return this.taskLinkedIssueService.findAll(params); + } + + /** + * Creates a new task linked issue. + * + * @param entity - The input data for creating a task linked issue. + * @returns A promise that resolves to the created task linked issue. + */ + @ApiOperation({ summary: 'Create Task Linked Issue' }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'The record has been successfully created.' + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid input. The response body may contain clues as to what went wrong.' + }) @HttpCode(HttpStatus.CREATED) @Permissions(PermissionsEnum.ALL_ORG_EDIT, PermissionsEnum.ORG_TASK_ADD) @Post() @UseValidationPipe({ whitelist: true }) async create(@Body() entity: CreateTaskLinkedIssueDTO): Promise { - return await this.taskLinkedIssueService.create(entity); + return this.commandBus.execute(new TaskLinkedIssueCreateCommand(entity)); } /** - * Update existing Linked Issue + * Updates an existing task linked issue. * - * @param id - * @param entity - * @returns + * @param id - The ID of the task linked issue to update. + * @param entity - The input data for updating the task linked issue. + * @returns A promise that resolves to the updated task linked issue. */ + @ApiOperation({ summary: 'Update an existing task linked issue' }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'The record has been successfully edited.' + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Record not found' + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid input. The response body may contain clues as to what went wrong.' + }) @HttpCode(HttpStatus.ACCEPTED) @Permissions(PermissionsEnum.ALL_ORG_EDIT, PermissionsEnum.ORG_TASK_EDIT) @Put(':id') @UseValidationPipe({ whitelist: true }) async update( - @Param('id', UUIDValidationPipe) id: ITaskLinkedIssue['id'], + @Param('id', UUIDValidationPipe) id: ID, @Body() entity: UpdateTaskLinkedIssueDTO ): Promise { - return await this.taskLinkedIssueService.create({ - ...entity, - id - }); + return this.commandBus.execute(new TaskLinkedIssueUpdateCommand(id, entity)); + } + + /** + * Deletes a task linked issue. + * + * @param id - The ID of the task linked issue to delete. + * @returns A promise that resolves to the result of the delete operation. + */ + @ApiOperation({ summary: 'Delete Task Linked Issue' }) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'The record has been successfully deleted' + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Record not found' + }) + @HttpCode(HttpStatus.ACCEPTED) + @Permissions(PermissionsEnum.ALL_ORG_EDIT, PermissionsEnum.ORG_TASK_DELETE) + @Delete(':id') + async delete(@Param('id', UUIDValidationPipe) id: ID): Promise { + return this.taskLinkedIssueService.delete(id); + } + + /** + * Soft deletes a task linked issue record. + * + * @param id - The ID of the task linked issue to soft delete. + * @returns A promise that resolves to the result of the soft delete operation. + */ + @ApiOperation({ summary: 'Soft delete Task Linked Issue record' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'The record has been successfully soft-deleted' + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Task Linked Issue record not found' + }) + @HttpCode(HttpStatus.ACCEPTED) + @Permissions(PermissionsEnum.ALL_ORG_EDIT, PermissionsEnum.ORG_TASK_DELETE) + @Delete(':id/soft') + @UseValidationPipe({ whitelist: true }) + async softRemove(@Param('id', UUIDValidationPipe) id: ID): Promise { + return this.taskLinkedIssueService.softDelete(id); } } diff --git a/packages/core/src/tasks/linked-issue/task-linked-issue.entity.ts b/packages/core/src/tasks/linked-issue/task-linked-issue.entity.ts index 3ccdd23db02..9685d0e1ae3 100644 --- a/packages/core/src/tasks/linked-issue/task-linked-issue.entity.ts +++ b/packages/core/src/tasks/linked-issue/task-linked-issue.entity.ts @@ -1,14 +1,7 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { - JoinColumn, - RelationId, -} from 'typeorm'; -import { IsEnum, IsUUID } from 'class-validator'; -import { - ITask, - ITaskLinkedIssue, - TaskRelatedIssuesRelationEnum, -} from '@gauzy/contracts'; +import { JoinColumn, RelationId } from 'typeorm'; +import { IsEnum, IsOptional, IsUUID } from 'class-validator'; +import { ID, ITask, ITaskLinkedIssue, TaskRelatedIssuesRelationEnum } from '@gauzy/contracts'; import { Task } from './../task.entity'; import { TenantOrganizationBaseEntity } from './../../core/entities/internal'; import { ColumnIndex, MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from './../../core/decorators/entity'; @@ -16,9 +9,9 @@ import { MikroOrmTaskLinkedIssueRepository } from './repository/mikro-orm-linked @MultiORMEntity('task_linked_issues', { mikroOrmRepository: () => MikroOrmTaskLinkedIssueRepository }) export class TaskLinkedIssue extends TenantOrganizationBaseEntity implements ITaskLinkedIssue { - @ApiProperty({ type: () => String, enum: TaskRelatedIssuesRelationEnum }) - @MultiORMColumn() + @ApiProperty({ enum: TaskRelatedIssuesRelationEnum }) @IsEnum(TaskRelatedIssuesRelationEnum) + @MultiORMColumn() action: TaskRelatedIssuesRelationEnum; /* @@ -27,6 +20,7 @@ export class TaskLinkedIssue extends TenantOrganizationBaseEntity implements ITa |-------------------------------------------------------------------------- */ @ApiPropertyOptional({ type: () => Task }) + @IsOptional() @MultiORMManyToOne(() => Task) @JoinColumn() taskFrom?: ITask; @@ -36,12 +30,13 @@ export class TaskLinkedIssue extends TenantOrganizationBaseEntity implements ITa @RelationId((it: TaskLinkedIssue) => it.taskFrom) @ColumnIndex() @MultiORMColumn({ relationId: true }) - taskFromId: ITask['id']; + taskFromId: ID; /** * Task Linked Issues */ @ApiPropertyOptional({ type: () => Object }) + @IsOptional() @MultiORMManyToOne(() => Task, (it) => it.linkedIssues) @JoinColumn() taskTo?: ITask; @@ -51,5 +46,5 @@ export class TaskLinkedIssue extends TenantOrganizationBaseEntity implements ITa @RelationId((it: TaskLinkedIssue) => it.taskTo) @ColumnIndex() @MultiORMColumn({ relationId: true }) - taskToId: ITask['id']; + taskToId: ID; } diff --git a/packages/core/src/tasks/linked-issue/task-linked-issue.helper.ts b/packages/core/src/tasks/linked-issue/task-linked-issue.helper.ts new file mode 100644 index 00000000000..d869a37ce97 --- /dev/null +++ b/packages/core/src/tasks/linked-issue/task-linked-issue.helper.ts @@ -0,0 +1,24 @@ +import { TaskRelatedIssuesRelationEnum } from '@gauzy/contracts'; + +/** + * Maps a task's related issue relation enum to a corresponding string description. + * + * @param {TaskRelatedIssuesRelationEnum} relation - The relation type from the enum `TaskRelatedIssuesRelationEnum`. + * @returns {string} The corresponding string description for the given relation type. + * @throws {Error} If the relation type is unsupported. + */ +export function taskRelatedIssueRelationMap(relation: TaskRelatedIssuesRelationEnum): string { + const issueRelationMap = { + [TaskRelatedIssuesRelationEnum.BLOCKS]: 'Blocks', + [TaskRelatedIssuesRelationEnum.CLONES]: 'Clones', + [TaskRelatedIssuesRelationEnum.DUPLICATES]: 'Duplicates', + [TaskRelatedIssuesRelationEnum.IS_BLOCKED_BY]: 'Is Blocked By', + [TaskRelatedIssuesRelationEnum.IS_CLONED_BY]: 'Is Cloned By', + [TaskRelatedIssuesRelationEnum.IS_DUPLICATED_BY]: 'Is Duplicated By', + [TaskRelatedIssuesRelationEnum.RELATES_TO]: 'Relates To' + } as const; + + return issueRelationMap[relation] ?? (() => { + throw new Error(`Unsupported relation type: ${relation}`); + })(); +} diff --git a/packages/core/src/tasks/linked-issue/task-linked-issue.module.ts b/packages/core/src/tasks/linked-issue/task-linked-issue.module.ts index cf3098adf43..fda06ec6a8c 100644 --- a/packages/core/src/tasks/linked-issue/task-linked-issue.module.ts +++ b/packages/core/src/tasks/linked-issue/task-linked-issue.module.ts @@ -5,8 +5,10 @@ import { RouterModule } from '@nestjs/core'; import { MikroOrmModule } from '@mikro-orm/nestjs'; import { RolePermissionModule } from '../../role-permission/role-permission.module'; import { TaskLinkedIssue } from './task-linked-issue.entity'; +import { CommandHandlers } from './commands/handlers'; import { TaskLinkedIssueController } from './task-linked-issue.controller'; import { TaskLinkedIssueService } from './task-linked-issue.service'; +import { TypeOrmTaskLinkedIssueRepository } from './repository/type-orm-linked-issue.repository'; @Module({ imports: [ @@ -17,7 +19,7 @@ import { TaskLinkedIssueService } from './task-linked-issue.service'; CqrsModule ], controllers: [TaskLinkedIssueController], - providers: [TaskLinkedIssueService], + providers: [TaskLinkedIssueService, TypeOrmTaskLinkedIssueRepository, ...CommandHandlers], exports: [TaskLinkedIssueService] }) -export class TaskLinkedIssueModule { } +export class TaskLinkedIssueModule {} diff --git a/packages/core/src/tasks/linked-issue/task-linked-issue.service.ts b/packages/core/src/tasks/linked-issue/task-linked-issue.service.ts index b713da8f458..6ca88ce08e6 100644 --- a/packages/core/src/tasks/linked-issue/task-linked-issue.service.ts +++ b/packages/core/src/tasks/linked-issue/task-linked-issue.service.ts @@ -1,18 +1,175 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { TaskLinkedIssue } from './task-linked-issue.entity'; +import { HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common'; +import { DeleteResult, FindOneOptions, UpdateResult } from 'typeorm'; +import { + ActionTypeEnum, + ActorTypeEnum, + BaseEntityEnum, + ID, + ITaskLinkedIssue, + ITaskLinkedIssueCreateInput, + ITaskLinkedIssueUpdateInput +} from '@gauzy/contracts'; import { TenantAwareCrudService } from '../../core/crud'; +import { RequestContext } from '../../core/context'; +import { ActivityLogService } from '../../activity-log/activity-log.service'; +import { TaskLinkedIssue } from './task-linked-issue.entity'; import { MikroOrmTaskLinkedIssueRepository } from './repository/mikro-orm-linked-issue.repository'; import { TypeOrmTaskLinkedIssueRepository } from './repository/type-orm-linked-issue.repository'; +import { taskRelatedIssueRelationMap } from './task-linked-issue.helper'; @Injectable() export class TaskLinkedIssueService extends TenantAwareCrudService { constructor( - @InjectRepository(TaskLinkedIssue) typeOrmTaskLinkedIssueRepository: TypeOrmTaskLinkedIssueRepository, - - mikroOrmTaskLinkedIssueRepository: MikroOrmTaskLinkedIssueRepository + mikroOrmTaskLinkedIssueRepository: MikroOrmTaskLinkedIssueRepository, + private readonly activityLogService: ActivityLogService ) { super(typeOrmTaskLinkedIssueRepository, mikroOrmTaskLinkedIssueRepository); } + + /** + * Creates a task linked to an issue. + * + * @param {ITaskLinkedIssueCreateInput} entity - The input data for creating a task linked issue. + * @returns {Promise} The created task linked issue. + * @throws {HttpException} Throws a Bad Request exception if task creation fails. + * + */ + async create(entity: ITaskLinkedIssueCreateInput): Promise { + const tenantId = RequestContext.currentTenantId() || entity.tenantId; + const { organizationId } = entity; + + try { + const taskLinkedIssue = await super.create({ ...entity, tenantId }); + + // Generate the activity log + this.activityLogService.logActivity( + BaseEntityEnum.TaskLinkedIssue, + ActionTypeEnum.Created, + ActorTypeEnum.User, + taskLinkedIssue.id, + taskRelatedIssueRelationMap(taskLinkedIssue.action), + taskLinkedIssue, + organizationId, + tenantId + ); + + // Return the created task linked issue + return taskLinkedIssue; + } catch (error) { + // Handle errors and return an appropriate error response + throw new HttpException(`Failed to create task linked issue : ${error.message}`, HttpStatus.BAD_REQUEST); + } + } + + /** + * Updates a task linked issue. + * + * @param {ID} id - The ID of the task linked issue to update. + * @param {ITaskLinkedIssueUpdateInput} input - The input data for updating the task linked issue. + * @returns {Promise} The updated task linked issue. + * @throws {HttpException} Throws a Bad Request exception if the update fails. + * @throws {NotFoundException} Throws a Not Found exception if the task linked issue does not exist. + * + */ + async update(id: ID, input: ITaskLinkedIssueUpdateInput): Promise { + const tenantId = RequestContext.currentTenantId() || input.tenantId; + + try { + // Retrieve existing task linked issue + const existingTaskLinkedIssue = await this.findOneByIdString(id); + + if (!existingTaskLinkedIssue) { + throw new NotFoundException('Task linked issue not found'); + } + + const updatedTaskLinkedIssue = await super.create({ ...input, tenantId, id }); + + // Generate the activity log + const { organizationId } = updatedTaskLinkedIssue; + this.activityLogService.logActivity( + BaseEntityEnum.TaskLinkedIssue, + ActionTypeEnum.Updated, + ActorTypeEnum.User, + updatedTaskLinkedIssue.id, + taskRelatedIssueRelationMap(updatedTaskLinkedIssue.action), + updatedTaskLinkedIssue, + organizationId, + tenantId, + existingTaskLinkedIssue, + input + ); + + // return the updated task linked issue + return updatedTaskLinkedIssue; + } catch (error) { + // Handle errors and return an appropriate error response + throw new HttpException(`Failed to update task linked issue: ${error.message}`, HttpStatus.BAD_REQUEST); + } + } + + /** + * Deletes a task linked issue and logs the deletion activity. + * + * @param id - The ID of the task linked issue to delete. + * @param options - Optional find options for the task linked issue. + * @returns A promise that resolves to the result of the delete operation. + */ + async delete(id: ID, options?: FindOneOptions): Promise { + try { + await this.deleteActivityLog(id); + return super.delete(id, options); + } catch (error) { + console.error(`Failed to delete task linked issue (ID: ${id}):`, error); + throw new HttpException(`Failed to delete task linked issue: ${error.message}`, HttpStatus.BAD_REQUEST); + } + } + + /** + * Soft deletes a task linked issue and logs the deletion activity. + * + * @param id - The ID of the task linked issue to soft delete. + * @returns A promise that resolves to the result of the soft delete operation or the deleted entity. + */ + async softDelete(id: ID): Promise { + try { + await this.deleteActivityLog(id); + return super.softDelete(id); + } catch (error) { + console.error(`Failed to soft delete task linked issue (ID: ${id}):`, error); + throw new HttpException(`Failed to soft delete task linked issue: ${error.message}`, HttpStatus.BAD_REQUEST); + } + } + + /** + * Deletes an activity log for a given task linked issue. + * + * @param id - The ID of the task linked issue to delete. + */ + private async deleteActivityLog(id: ID) { + const tenantId = RequestContext.currentTenantId(); + + try { + // Retrieve existing task linked issue + const existingTaskLinkedIssue = await this.findOneByIdString(id); + if (!existingTaskLinkedIssue) { + throw new NotFoundException('Task linked issue not found'); + } + + // Generate deleted activity log + const { organizationId } = existingTaskLinkedIssue; + this.activityLogService.logActivity( + BaseEntityEnum.TaskLinkedIssue, + ActionTypeEnum.Deleted, + ActorTypeEnum.User, + id, + taskRelatedIssueRelationMap(existingTaskLinkedIssue.action), + existingTaskLinkedIssue, + organizationId, + tenantId + ); + } catch (error) { + console.error(`Failed to create activity log for deletion (ID: ${id}):`, error); + } + } } diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index 576518b8590..a4f2f4b31a9 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -93,7 +93,7 @@ export class TaskService extends TenantAwareCrudService { // Register Task Sprint moving history if (organizationSprintId && organizationSprintId !== task.organizationSprintId) { await this.typeOrmOrganizationSprintTaskHistoryRepository.save({ - fromSprintId: task.organizationSprintId, + fromSprintId: task.organizationSprintId || organizationSprintId, // Use incoming sprint ID if the task's organizationSprintId was previously null or undefined toSprintId: organizationSprintId, taskId: updatedTask.id, movedById: userId,