Files
CRM_Montzar/apps/api/src/activities/activities.controller.ts
T
Mahdi e6aa4c64f0 feat: group activities into boards with their own stages, like sales pipelines
Existing stages and activities move into a default board per environment; new activities pick a board in the form.
2026-10-20 17:23:27 +03:30

354 lines
9.5 KiB
TypeScript

import {
BadRequestException,
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
Res,
StreamableFile,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import type { Response } from 'express';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
import { CurrentUser } from '../auth/current-user.decorator';
import type { Actor } from '../common/record-scope';
import { ActivitiesService } from './activities.service';
import { ActivitiesExcelService } from './activities-excel.service';
import { ActivityStagesService } from './activity-stages.service';
import { ActivityTypesService } from './activity-types.service';
import {
CreateActivityBoardDto,
CreateActivityDto,
CreateActivityStageDto,
CreateActivityTypeDto,
ImportActivityRowsDto,
ListActivitiesQueryDto,
ListActivityStagesQueryDto,
MoveActivityStageDto,
ReorderActivityStagesDto,
ReorderActivityTypesDto,
UpdateActivityBoardDto,
UpdateActivityDto,
UpdateActivityStageDto,
UpdateActivityTypeDto,
} from './dto';
import { brand } from '@mento/shared';
const excelUpload = FileInterceptor('file', {
storage: memoryStorage(),
limits: { fileSize: 15_000_000 },
});
@Controller('activities')
@UseGuards(JwtAuthGuard)
export class ActivitiesController {
constructor(
private readonly activitiesService: ActivitiesService,
private readonly excelService: ActivitiesExcelService,
private readonly stagesService: ActivityStagesService,
private readonly typesService: ActivityTypesService,
) {}
@Get('summary')
summary(
@CurrentUser() user: Actor,
@Query('desk') desk?: string,
) {
const scope = desk === 'mine' || desk === 'team' ? desk : undefined;
return this.activitiesService.summary(user.organizationId, user, scope);
}
@Get('boards')
listBoards(@CurrentUser() user: Actor) {
return this.stagesService.listBoards(user.organizationId);
}
@Post('boards')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
createBoard(@CurrentUser() user: Actor, @Body() dto: CreateActivityBoardDto) {
return this.stagesService.createBoard(user.organizationId, dto.name);
}
@Patch('boards/:id')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
updateBoard(
@CurrentUser() user: Actor,
@Param('id') id: string,
@Body() dto: UpdateActivityBoardDto,
) {
return this.stagesService.updateBoard(user.organizationId, id, dto);
}
@Delete('boards/:id')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
async removeBoard(@CurrentUser() user: Actor, @Param('id') id: string) {
await this.stagesService.removeBoard(user.organizationId, id);
return { ok: true };
}
@Get('stages')
listStages(@CurrentUser() user: Actor, @Query() query: ListActivityStagesQueryDto) {
return this.stagesService.list(user.organizationId, query.boardId);
}
@Post('stages')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
createStage(@CurrentUser() user: Actor, @Body() dto: CreateActivityStageDto) {
return this.stagesService.create(user.organizationId, dto);
}
@Patch('stages/reorder')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
reorderStages(@CurrentUser() user: Actor, @Body() dto: ReorderActivityStagesDto) {
return this.stagesService.reorder(user.organizationId, dto.stageIds);
}
@Patch('stages/:id')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
updateStage(
@CurrentUser() user: Actor,
@Param('id') id: string,
@Body() dto: UpdateActivityStageDto,
) {
return this.stagesService.update(user.organizationId, id, dto);
}
@Delete('stages/:id')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
async removeStage(@CurrentUser() user: Actor, @Param('id') id: string) {
await this.stagesService.remove(user.organizationId, id);
return { ok: true };
}
@Get('types')
listTypes(@CurrentUser() user: Actor) {
return this.typesService.list(user.organizationId);
}
@Post('types')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
createType(@CurrentUser() user: Actor, @Body() dto: CreateActivityTypeDto) {
return this.typesService.create(user.organizationId, dto);
}
@Patch('types/reorder')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
reorderTypes(@CurrentUser() user: Actor, @Body() dto: ReorderActivityTypesDto) {
return this.typesService.reorder(user.organizationId, dto.typeIds);
}
@Patch('types/:id')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
updateType(
@CurrentUser() user: Actor,
@Param('id') id: string,
@Body() dto: UpdateActivityTypeDto,
) {
return this.typesService.update(user.organizationId, id, dto);
}
@Delete('types/:id')
@UseGuards(RolesGuard)
@Roles('owner', 'admin')
async removeType(@CurrentUser() user: Actor, @Param('id') id: string) {
await this.typesService.remove(user.organizationId, id);
return { ok: true };
}
@Get('stage-counts')
stageCounts(
@CurrentUser() user: Actor,
@Query() query: ListActivitiesQueryDto,
) {
return this.activitiesService.stageCounts(
user.organizationId,
user,
query.filter ?? 'all',
query.q,
query.desk,
{
type: query.type,
typeDefId: query.typeDefId,
status: query.status,
assigneeId: query.assigneeId,
boardId: query.boardId,
},
);
}
@Get()
list(
@CurrentUser() user: Actor,
@Query() query: ListActivitiesQueryDto,
) {
return this.activitiesService.list(
user.organizationId,
user,
query.filter ?? 'today',
query.q,
query.page ?? 1,
query.limit ?? 50,
{
personId: query.personId,
companyId: query.companyId,
dealId: query.dealId,
leadId: query.leadId,
},
{
from: query.from,
to: query.to,
},
query.desk,
{
type: query.type,
typeDefId: query.typeDefId,
status: query.status,
assigneeId: query.assigneeId,
boardId: query.boardId,
},
query.stageId ? { stageId: query.stageId } : undefined,
);
}
@Get('export')
async export(
@CurrentUser() user: Actor,
@Res({ passthrough: true }) res: Response,
) {
const buffer = await this.excelService.export(user);
res.set({
'Content-Type':
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Content-Disposition': `attachment; filename="${brand.filePrefix}-activities.xlsx"`,
});
return new StreamableFile(buffer);
}
@Post('import/preview')
@UseInterceptors(excelUpload)
importPreview(@UploadedFile() file?: Express.Multer.File) {
if (!file?.buffer?.length) {
throw new BadRequestException('فایل ارسال نشده');
}
return this.excelService.preview(file);
}
@Post('import/prepare')
@UseInterceptors(excelUpload)
importPrepare(
@CurrentUser() user: Actor,
@UploadedFile() file?: Express.Multer.File,
@Body('mapping') mapping?: string,
) {
if (!file?.buffer?.length) {
throw new BadRequestException('فایل ارسال نشده');
}
return this.excelService.prepare(user, file, mapping);
}
@Get('import/prepare/:sessionId')
importPrepareStatus(
@CurrentUser() user: Actor,
@Param('sessionId') sessionId: string,
) {
return this.excelService.prepareStatus(user, sessionId);
}
@Post('import/rows')
importRows(
@CurrentUser() user: Actor,
@Body() dto: ImportActivityRowsDto,
) {
return this.excelService.importRows(user, dto);
}
@Post('import')
@UseInterceptors(excelUpload)
import(
@CurrentUser() user: Actor,
@UploadedFile() file?: Express.Multer.File,
@Body('mapping') mapping?: string,
@Body('sessionId') sessionId?: string,
@Body('offset') offset?: string,
@Body('limit') limit?: string,
) {
if (!file?.buffer?.length && !sessionId) {
throw new BadRequestException('فایل ارسال نشده');
}
return this.excelService.import(user, file, mapping, {
sessionId,
offset,
limit,
});
}
@Get(':id')
get(@CurrentUser() user: Actor, @Param('id') id: string) {
return this.activitiesService.get(user.organizationId, id, user);
}
@Post(':id/promote-from-prior')
promoteFromPrior(@CurrentUser() user: Actor, @Param('id') id: string) {
return this.activitiesService.promoteFromPrior(user, id);
}
@Post()
create(@CurrentUser() user: Actor, @Body() dto: CreateActivityDto) {
return this.activitiesService.create(
user.organizationId,
user.userId,
dto,
user,
);
}
@Patch(':id')
update(
@CurrentUser() user: Actor,
@Param('id') id: string,
@Body() dto: UpdateActivityDto,
) {
return this.activitiesService.update(user.organizationId, id, user, dto);
}
@Patch(':id/stage')
moveStage(
@CurrentUser() user: Actor,
@Param('id') id: string,
@Body() dto: MoveActivityStageDto,
) {
return this.activitiesService.moveToStage(user.organizationId, id, user, dto.stageId);
}
@Patch(':id/complete')
complete(@CurrentUser() user: Actor, @Param('id') id: string) {
return this.activitiesService.complete(user.organizationId, id, user);
}
@Delete(':id')
async remove(@CurrentUser() user: Actor, @Param('id') id: string) {
await this.activitiesService.remove(user.organizationId, id, user);
return { ok: true };
}
}