INSIGHT-MVP/packages/crm-service/src/deals/deals.controller.ts
Thomas Reitz 48df3c3144 feat(crm): Phase 1 backend schema expansion + frontend integration
Backend (CRM-Expert Phase 1):
- New enums: ContactSource, EntityStatus, CompanySize, OwnerRole,
  LostReason, EmailType, PhoneType
- Contact: add linkedinUrl, birthday, source, department, status
- Company: add vatId, taxId, tradeRegisterNumber, registerCourt,
  companySize, deliveryAddress, dataEnrichedAt/Source, status
- Deal: add lostReason + lostReasonText (required when status=LOST)
- Multi-value emails/phones tables (contact_emails, contact_phones)
- Owner m:n model (contact_owners, company_owners, deal_owners)
- Redis Pub/Sub CRM events (crm.contact.created, crm.deal.won, etc.)
- Activity due_soon scheduler (cron every 15 min)
- SQL migration with data migration for existing records

Frontend integration:
- types.ts: all new enums, interfaces, label maps
- api.ts: owner CRUD endpoints (add/remove for contacts/companies/deals)
- hooks.ts: 6 new owner mutation hooks
- ContactFormModal: LinkedIn, birthday, source, department, status fields
- ContactDetailPage: display new fields (LinkedIn, department, birthday,
  source, status badge)
- CompanyDetailPage: display vatId, taxId, trade register, company size,
  delivery address, data enrichment info
- DealFormModal: lost reason dropdown + text (shown when status=LOST)
- DealDetailPage: display lost reason with label
- CompaniesPage: EntityStatus-aware status dots (ACTIVE/INACTIVE/BLOCKED)
- ActivityType: add FOLLOWUP to all label maps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:56:41 +01:00

150 lines
3.9 KiB
TypeScript

import {
Controller,
Get,
Post,
Patch,
Delete,
Body,
Param,
Query,
ParseUUIDPipe,
HttpCode,
HttpStatus,
UseGuards,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiBearerAuth,
ApiParam,
} from '@nestjs/swagger';
import { DealsService } from './deals.service';
import { CreateDealDto } from './dto/create-deal.dto';
import { UpdateDealDto } from './dto/update-deal.dto';
import { QueryDealsDto } from './dto/query-deals.dto';
import { AddOwnerDto } from '../common/dto/owner.dto';
import { OwnersService } from '../owners/owners.service';
import { CurrentUser, JwtPayload } from '../common/decorators';
import { TenantGuard } from '../auth/guards/tenant.guard';
import {
paginatedResponse,
singleResponse,
} from '../common/dto/pagination.dto';
@ApiTags('Vorgaenge (Deals)')
@ApiBearerAuth('access-token')
@UseGuards(TenantGuard)
@Controller('deals')
export class DealsController {
constructor(
private readonly dealsService: DealsService,
private readonly ownersService: OwnersService,
) {}
@Post()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Vorgang erstellen' })
async create(
@CurrentUser() user: JwtPayload,
@Body() dto: CreateDealDto,
) {
const deal = await this.dealsService.create(
user.tenantId!,
user.sub,
dto,
);
return singleResponse(deal);
}
@Get()
@ApiOperation({ summary: 'Vorgaenge auflisten (paginiert, filterbar)' })
async findAll(
@CurrentUser() user: JwtPayload,
@Query() query: QueryDealsDto,
) {
const result = await this.dealsService.findAll(user.tenantId!, query);
return paginatedResponse(
result.data,
result.total,
result.page,
result.pageSize,
);
}
@Get(':id')
@ApiOperation({ summary: 'Vorgangsdetails abrufen' })
@ApiParam({ name: 'id', type: 'string', format: 'uuid' })
async findOne(
@CurrentUser() user: JwtPayload,
@Param('id', ParseUUIDPipe) id: string,
) {
const deal = await this.dealsService.findOne(user.tenantId!, id);
return singleResponse(deal);
}
@Patch(':id')
@ApiOperation({ summary: 'Vorgang aktualisieren' })
@ApiParam({ name: 'id', type: 'string', format: 'uuid' })
async update(
@CurrentUser() user: JwtPayload,
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: UpdateDealDto,
) {
const deal = await this.dealsService.update(
user.tenantId!,
id,
user.sub,
dto,
);
return singleResponse(deal);
}
@Delete(':id')
@ApiOperation({ summary: 'Vorgang loeschen' })
@ApiParam({ name: 'id', type: 'string', format: 'uuid' })
async remove(
@CurrentUser() user: JwtPayload,
@Param('id', ParseUUIDPipe) id: string,
) {
const deal = await this.dealsService.remove(user.tenantId!, id);
return singleResponse(deal);
}
// --------------------------------------------------------
// Owner Endpoints
// --------------------------------------------------------
@Post(':id/owners')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Owner zu Vorgang hinzufuegen' })
@ApiParam({ name: 'id', type: 'string', format: 'uuid' })
async addOwner(
@CurrentUser() user: JwtPayload,
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: AddOwnerDto,
) {
const owner = await this.ownersService.addDealOwner(
user.tenantId!,
id,
dto,
);
return singleResponse(owner);
}
@Delete(':id/owners/:userId')
@ApiOperation({ summary: 'Owner von Vorgang entfernen' })
@ApiParam({ name: 'id', type: 'string', format: 'uuid' })
@ApiParam({ name: 'userId', type: 'string', format: 'uuid' })
async removeOwner(
@CurrentUser() user: JwtPayload,
@Param('id', ParseUUIDPipe) id: string,
@Param('userId', ParseUUIDPipe) userId: string,
) {
const owner = await this.ownersService.removeDealOwner(
user.tenantId!,
id,
userId,
);
return singleResponse(owner);
}
}