Add project board, loyalty club, and if/then automations.

Separate modules for post-sales kanban, person points/tiers, and deal-won/ticket-resolved rules without overlapping the sales pipeline.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 09:42:07 +03:30
co-authored by Cursor
parent 2e6c4779ab
commit c2bfa7749d
29 changed files with 3126 additions and 9 deletions
+3 -2
View File
@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common';
import { Module, forwardRef } from '@nestjs/common';
import { NotificationsModule } from '../notifications/notifications.module';
import { AutomationsModule } from '../automations/automations.module';
import { TicketsController } from './tickets.controller';
import { TicketsService } from './tickets.service';
@Module({
imports: [NotificationsModule],
imports: [NotificationsModule, forwardRef(() => AutomationsModule)],
controllers: [TicketsController],
providers: [TicketsService],
exports: [TicketsService],
+21
View File
@@ -20,6 +20,7 @@ import {
ticketAccessWhere,
} from '../common/record-scope';
import { NotificationsService } from '../notifications/notifications.service';
import { AutomationsService } from '../automations/automations.service';
const include = {
person: { select: { id: true, firstName: true, lastName: true } },
@@ -40,6 +41,7 @@ export class TicketsService {
constructor(
private readonly prisma: PrismaService,
private readonly notifications: NotificationsService,
private readonly automations: AutomationsService,
) {}
async list(
@@ -325,6 +327,25 @@ export class TicketsService {
}
await this.prisma.ticket.update({ where: { id }, data });
const becameResolved =
(dto.status === 'resolved' || dto.status === 'closed') &&
existing.status !== 'resolved' &&
existing.status !== 'closed';
if (becameResolved) {
await this.automations.runTrigger('ticket_resolved', {
organizationId: actor.organizationId,
entityType: 'ticket',
entityId: existing.id,
title: existing.subject,
personId: existing.personId,
companyId: existing.companyId,
dealId: existing.dealId,
assigneeId: existing.assigneeId,
ownerId: existing.assigneeId,
});
}
return this.get(actor, id);
}