feat: keep deletions restorable and confirm a pipeline's deals before it is removed

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-13 22:23:09 +03:30
co-authored by Cursor
parent c04582072d
commit 2cc81bb596
17 changed files with 1158 additions and 6 deletions
@@ -69,6 +69,15 @@ export class PipelinesController {
return this.pipelinesService.merge(user.organizationId, pipelineId, dto);
}
@Delete(':pipelineId')
@Roles('owner', 'admin')
remove(
@CurrentUser() user: { organizationId: string },
@Param('pipelineId') pipelineId: string,
) {
return this.pipelinesService.remove(user.organizationId, pipelineId);
}
@Post(':pipelineId/stages')
@Roles('owner', 'admin')
createStage(
+35 -1
View File
@@ -107,7 +107,18 @@ export class PipelinesService {
include: { stages: { orderBy: { position: 'asc' } } },
orderBy: [{ isDefault: 'desc' }, { name: 'asc' }],
});
return rows.map((row) => this.toPipelineDto(row));
const counts = await this.prisma.deal.groupBy({
by: ['pipelineId'],
where: { organizationId },
_count: { _all: true },
});
const dealCountByPipeline = new Map(
counts.map((row) => [row.pipelineId, row._count._all]),
);
return rows.map((row) => ({
...this.toPipelineDto(row),
dealCount: dealCountByPipeline.get(row.id) ?? 0,
}));
}
async create(
@@ -192,6 +203,29 @@ export class PipelinesService {
return this.getOwnedPipelineDto(organizationId, pipeline.id);
}
async remove(organizationId: string, pipelineId: string): Promise<void> {
const pipeline = await this.getOwnedPipeline(organizationId, pipelineId);
await this.prisma.$transaction(async (tx) => {
await tx.deal.deleteMany({
where: { organizationId, pipelineId: pipeline.id },
});
if (pipeline.isDefault) {
const next = await tx.pipeline.findFirst({
where: { organizationId, id: { not: pipeline.id } },
orderBy: { createdAt: 'asc' },
select: { id: true },
});
if (next) {
await tx.pipeline.update({
where: { id: next.id },
data: { isDefault: true },
});
}
}
await tx.pipeline.delete({ where: { id: pipeline.id } });
});
}
async deleteStage(
organizationId: string,
pipelineId: string,