mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 01:56:39 +02:00
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>
166 lines
4.5 KiB
TypeScript
166 lines
4.5 KiB
TypeScript
// ============================================================
|
|
// Shared Owner Service fuer Contact, Company, Deal
|
|
// ============================================================
|
|
|
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
import { CrmPrismaService } from '../prisma/crm-prisma.service';
|
|
import { AddOwnerDto } from '../common/dto/owner.dto';
|
|
|
|
@Injectable()
|
|
export class OwnersService {
|
|
constructor(private readonly prisma: CrmPrismaService) {}
|
|
|
|
// --------------------------------------------------------
|
|
// Contact Owners
|
|
// --------------------------------------------------------
|
|
|
|
async addContactOwner(tenantId: string, contactId: string, dto: AddOwnerDto) {
|
|
// Contact existiert + gehoert zum Tenant
|
|
const contact = await this.prisma.contact.findFirst({
|
|
where: { id: contactId, tenantId },
|
|
});
|
|
if (!contact) {
|
|
throw new NotFoundException('Kontakt nicht gefunden');
|
|
}
|
|
|
|
// Upsert: Falls Owner bereits existiert, Rolle aktualisieren
|
|
return this.prisma.contactOwner.upsert({
|
|
where: {
|
|
contactId_userId: { contactId, userId: dto.userId },
|
|
},
|
|
create: {
|
|
tenantId,
|
|
contactId,
|
|
userId: dto.userId,
|
|
role: dto.role ?? 'OWNER',
|
|
},
|
|
update: {
|
|
role: dto.role ?? 'OWNER',
|
|
},
|
|
});
|
|
}
|
|
|
|
async removeContactOwner(tenantId: string, contactId: string, userId: string) {
|
|
// Contact existiert + gehoert zum Tenant
|
|
const contact = await this.prisma.contact.findFirst({
|
|
where: { id: contactId, tenantId },
|
|
});
|
|
if (!contact) {
|
|
throw new NotFoundException('Kontakt nicht gefunden');
|
|
}
|
|
|
|
// Owner suchen
|
|
const owner = await this.prisma.contactOwner.findUnique({
|
|
where: {
|
|
contactId_userId: { contactId, userId },
|
|
},
|
|
});
|
|
if (!owner) {
|
|
throw new NotFoundException('Owner nicht gefunden');
|
|
}
|
|
|
|
return this.prisma.contactOwner.delete({
|
|
where: { id: owner.id },
|
|
});
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Company Owners
|
|
// --------------------------------------------------------
|
|
|
|
async addCompanyOwner(tenantId: string, companyId: string, dto: AddOwnerDto) {
|
|
const company = await this.prisma.company.findFirst({
|
|
where: { id: companyId, tenantId },
|
|
});
|
|
if (!company) {
|
|
throw new NotFoundException('Unternehmen nicht gefunden');
|
|
}
|
|
|
|
return this.prisma.companyOwner.upsert({
|
|
where: {
|
|
companyId_userId: { companyId, userId: dto.userId },
|
|
},
|
|
create: {
|
|
tenantId,
|
|
companyId,
|
|
userId: dto.userId,
|
|
role: dto.role ?? 'OWNER',
|
|
},
|
|
update: {
|
|
role: dto.role ?? 'OWNER',
|
|
},
|
|
});
|
|
}
|
|
|
|
async removeCompanyOwner(tenantId: string, companyId: string, userId: string) {
|
|
const company = await this.prisma.company.findFirst({
|
|
where: { id: companyId, tenantId },
|
|
});
|
|
if (!company) {
|
|
throw new NotFoundException('Unternehmen nicht gefunden');
|
|
}
|
|
|
|
const owner = await this.prisma.companyOwner.findUnique({
|
|
where: {
|
|
companyId_userId: { companyId, userId },
|
|
},
|
|
});
|
|
if (!owner) {
|
|
throw new NotFoundException('Owner nicht gefunden');
|
|
}
|
|
|
|
return this.prisma.companyOwner.delete({
|
|
where: { id: owner.id },
|
|
});
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Deal Owners
|
|
// --------------------------------------------------------
|
|
|
|
async addDealOwner(tenantId: string, dealId: string, dto: AddOwnerDto) {
|
|
const deal = await this.prisma.deal.findFirst({
|
|
where: { id: dealId, tenantId },
|
|
});
|
|
if (!deal) {
|
|
throw new NotFoundException('Vorgang nicht gefunden');
|
|
}
|
|
|
|
return this.prisma.dealOwner.upsert({
|
|
where: {
|
|
dealId_userId: { dealId, userId: dto.userId },
|
|
},
|
|
create: {
|
|
tenantId,
|
|
dealId,
|
|
userId: dto.userId,
|
|
role: dto.role ?? 'OWNER',
|
|
},
|
|
update: {
|
|
role: dto.role ?? 'OWNER',
|
|
},
|
|
});
|
|
}
|
|
|
|
async removeDealOwner(tenantId: string, dealId: string, userId: string) {
|
|
const deal = await this.prisma.deal.findFirst({
|
|
where: { id: dealId, tenantId },
|
|
});
|
|
if (!deal) {
|
|
throw new NotFoundException('Vorgang nicht gefunden');
|
|
}
|
|
|
|
const owner = await this.prisma.dealOwner.findUnique({
|
|
where: {
|
|
dealId_userId: { dealId, userId },
|
|
},
|
|
});
|
|
if (!owner) {
|
|
throw new NotFoundException('Owner nicht gefunden');
|
|
}
|
|
|
|
return this.prisma.dealOwner.delete({
|
|
where: { id: owner.id },
|
|
});
|
|
}
|
|
}
|