Editing of the project's Kariz page
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
@@ -16,6 +17,27 @@ export class CreateProjectBoardDto {
|
||||
name!: string;
|
||||
}
|
||||
|
||||
export class CreateProjectBoardStageDto {
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(80)
|
||||
name!: string;
|
||||
}
|
||||
|
||||
export class UpdateProjectBoardStageDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(80)
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export class ReorderProjectBoardStagesDto {
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
stageIds!: string[];
|
||||
}
|
||||
|
||||
export class CreateProjectCardDto {
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
|
||||
@@ -23,8 +23,11 @@ import { RolesGuard } from '../auth/roles.guard';
|
||||
import type { Actor } from '../common/record-scope';
|
||||
import {
|
||||
CreateProjectBoardDto,
|
||||
CreateProjectBoardStageDto,
|
||||
CreateProjectCardDto,
|
||||
MoveProjectCardDto,
|
||||
ReorderProjectBoardStagesDto,
|
||||
UpdateProjectBoardStageDto,
|
||||
UpdateProjectCardDto,
|
||||
} from './dto';
|
||||
import { ProjectBoardsService } from './project-boards.service';
|
||||
@@ -61,6 +64,56 @@ export class ProjectBoardsController {
|
||||
return this.boards.create(user.organizationId, dto.name);
|
||||
}
|
||||
|
||||
@Post('project-boards/:boardId/stages')
|
||||
@Roles('owner', 'admin')
|
||||
createStage(
|
||||
@CurrentUser() user: Actor,
|
||||
@Param('boardId') boardId: string,
|
||||
@Body() dto: CreateProjectBoardStageDto,
|
||||
) {
|
||||
return this.boards.createStage(user.organizationId, boardId, dto.name);
|
||||
}
|
||||
|
||||
@Patch('project-boards/:boardId/stages/order')
|
||||
@Roles('owner', 'admin')
|
||||
reorderStages(
|
||||
@CurrentUser() user: Actor,
|
||||
@Param('boardId') boardId: string,
|
||||
@Body() dto: ReorderProjectBoardStagesDto,
|
||||
) {
|
||||
return this.boards.reorderStages(
|
||||
user.organizationId,
|
||||
boardId,
|
||||
dto.stageIds,
|
||||
);
|
||||
}
|
||||
|
||||
@Patch('project-boards/:boardId/stages/:stageId')
|
||||
@Roles('owner', 'admin')
|
||||
updateStage(
|
||||
@CurrentUser() user: Actor,
|
||||
@Param('boardId') boardId: string,
|
||||
@Param('stageId') stageId: string,
|
||||
@Body() dto: UpdateProjectBoardStageDto,
|
||||
) {
|
||||
return this.boards.updateStage(
|
||||
user.organizationId,
|
||||
boardId,
|
||||
stageId,
|
||||
dto.name,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('project-boards/:boardId/stages/:stageId')
|
||||
@Roles('owner', 'admin')
|
||||
deleteStage(
|
||||
@CurrentUser() user: Actor,
|
||||
@Param('boardId') boardId: string,
|
||||
@Param('stageId') stageId: string,
|
||||
) {
|
||||
return this.boards.deleteStage(user.organizationId, boardId, stageId);
|
||||
}
|
||||
|
||||
@Get('project-cards/export')
|
||||
async exportCards(
|
||||
@CurrentUser() user: Actor,
|
||||
|
||||
@@ -299,6 +299,114 @@ export class ProjectBoardsService {
|
||||
await this.prisma.projectCard.delete({ where: { id } });
|
||||
}
|
||||
|
||||
async createStage(
|
||||
organizationId: string,
|
||||
boardId: string,
|
||||
name: string,
|
||||
): Promise<ProjectBoardDto> {
|
||||
const board = await this.getOwnedBoard(organizationId, boardId);
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) throw new BadRequestException('نام ستون لازم است');
|
||||
await this.prisma.projectBoardStage.create({
|
||||
data: {
|
||||
boardId: board.id,
|
||||
name: trimmed,
|
||||
position: board.stages.length,
|
||||
},
|
||||
});
|
||||
return this.getOwnedBoardDto(organizationId, board.id);
|
||||
}
|
||||
|
||||
async updateStage(
|
||||
organizationId: string,
|
||||
boardId: string,
|
||||
stageId: string,
|
||||
name?: string,
|
||||
): Promise<ProjectBoardDto> {
|
||||
const board = await this.getOwnedBoard(organizationId, boardId);
|
||||
const stage = board.stages.find((s) => s.id === stageId);
|
||||
if (!stage) throw new NotFoundException('ستون پیدا نشد');
|
||||
const trimmed = name?.trim() ?? '';
|
||||
if (!trimmed) throw new BadRequestException('نام ستون لازم است');
|
||||
await this.prisma.projectBoardStage.update({
|
||||
where: { id: stage.id },
|
||||
data: { name: trimmed },
|
||||
});
|
||||
return this.getOwnedBoardDto(organizationId, board.id);
|
||||
}
|
||||
|
||||
async deleteStage(
|
||||
organizationId: string,
|
||||
boardId: string,
|
||||
stageId: string,
|
||||
): Promise<ProjectBoardDto> {
|
||||
const board = await this.getOwnedBoard(organizationId, boardId);
|
||||
const stage = board.stages.find((s) => s.id === stageId);
|
||||
if (!stage) throw new NotFoundException('ستون پیدا نشد');
|
||||
if (board.stages.length <= 1) {
|
||||
throw new BadRequestException('حداقل یک ستون باید باقی بماند');
|
||||
}
|
||||
const cardCount = await this.prisma.projectCard.count({
|
||||
where: { organizationId, stageId: stage.id },
|
||||
});
|
||||
if (cardCount > 0) {
|
||||
throw new BadRequestException(
|
||||
'ابتدا کارتهای این ستون را به ستون دیگری منتقل کنید',
|
||||
);
|
||||
}
|
||||
const remaining = board.stages.filter((s) => s.id !== stage.id);
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.projectBoardStage.delete({ where: { id: stage.id } }),
|
||||
...remaining.map((s, index) =>
|
||||
this.prisma.projectBoardStage.update({
|
||||
where: { id: s.id },
|
||||
data: { position: index },
|
||||
}),
|
||||
),
|
||||
]);
|
||||
return this.getOwnedBoardDto(organizationId, board.id);
|
||||
}
|
||||
|
||||
async reorderStages(
|
||||
organizationId: string,
|
||||
boardId: string,
|
||||
stageIds: string[],
|
||||
): Promise<ProjectBoardDto> {
|
||||
const board = await this.getOwnedBoard(organizationId, boardId);
|
||||
const currentIds = board.stages.map((s) => s.id);
|
||||
const unique = new Set(stageIds);
|
||||
if (
|
||||
stageIds.length !== currentIds.length ||
|
||||
unique.size !== currentIds.length ||
|
||||
currentIds.some((id) => !unique.has(id))
|
||||
) {
|
||||
throw new BadRequestException('ترتیب ستونها نامعتبر است');
|
||||
}
|
||||
await this.prisma.$transaction(
|
||||
stageIds.map((id, index) =>
|
||||
this.prisma.projectBoardStage.update({
|
||||
where: { id },
|
||||
data: { position: index },
|
||||
}),
|
||||
),
|
||||
);
|
||||
return this.getOwnedBoardDto(organizationId, board.id);
|
||||
}
|
||||
|
||||
private async getOwnedBoard(organizationId: string, boardId: string) {
|
||||
const board = await this.prisma.projectBoard.findFirst({
|
||||
where: { id: boardId, organizationId },
|
||||
include: { stages: { orderBy: { position: 'asc' as const } } },
|
||||
});
|
||||
if (!board) throw new NotFoundException('برد پیدا نشد');
|
||||
return board;
|
||||
}
|
||||
|
||||
private async getOwnedBoardDto(organizationId: string, boardId: string) {
|
||||
const board = await this.getOwnedBoard(organizationId, boardId);
|
||||
return this.toBoardDto(board);
|
||||
}
|
||||
|
||||
toCardDto(row: {
|
||||
id: string;
|
||||
title: string;
|
||||
|
||||
@@ -108,6 +108,7 @@ export class ProjectCardsExcelService {
|
||||
async import(actor: Actor, file: Express.Multer.File): Promise<ImportContactsResult> {
|
||||
const rows = await readMappedRows(file, CASE_ALIASES);
|
||||
const result: ImportContactsResult = { created: 0, skipped: 0, errors: [] };
|
||||
const affected = new Map<string, string>();
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i]!;
|
||||
@@ -122,12 +123,13 @@ export class ProjectCardsExcelService {
|
||||
}
|
||||
|
||||
try {
|
||||
const { boardId, stageId } = await this.resolveBoardAndStage(
|
||||
const { boardId, stageId, boardName } = await this.resolveBoardAndStage(
|
||||
actor.organizationId,
|
||||
row.boardName,
|
||||
row.stageName,
|
||||
isClosedStatus(row.status),
|
||||
);
|
||||
affected.set(boardId, boardName);
|
||||
const duplicate = await this.prisma.projectCard.findFirst({
|
||||
where: { organizationId: actor.organizationId, boardId, title },
|
||||
select: { id: true },
|
||||
@@ -165,6 +167,12 @@ export class ProjectCardsExcelService {
|
||||
}
|
||||
}
|
||||
|
||||
const entries = [...affected.entries()];
|
||||
if (entries[0]) {
|
||||
result.boardId = entries[0][0];
|
||||
result.boards = entries.map(([, n]) => n);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -204,19 +212,19 @@ export class ProjectCardsExcelService {
|
||||
const existing = board.stages.find(
|
||||
(s) => s.name.trim().toLowerCase() === wanted.toLowerCase(),
|
||||
);
|
||||
if (existing) return { boardId: board.id, stageId: existing.id };
|
||||
if (existing) return { boardId: board.id, stageId: existing.id, boardName: board.name };
|
||||
const maxPos = board.stages.reduce((m, s) => Math.max(m, s.position), -1);
|
||||
const stage = await this.prisma.projectBoardStage.create({
|
||||
data: { boardId: board.id, name: wanted, position: maxPos + 1 },
|
||||
});
|
||||
return { boardId: board.id, stageId: stage.id };
|
||||
return { boardId: board.id, stageId: stage.id, boardName: board.name };
|
||||
}
|
||||
|
||||
const stage = closed
|
||||
? board.stages[board.stages.length - 1]
|
||||
: board.stages[0];
|
||||
if (!stage) throw new Error('برد مرحلهای ندارد');
|
||||
return { boardId: board.id, stageId: stage.id };
|
||||
return { boardId: board.id, stageId: stage.id, boardName: board.name };
|
||||
}
|
||||
|
||||
private async findPersonId(organizationId: string, row: Record<string, string>) {
|
||||
|
||||
Reference in New Issue
Block a user