Ask activity reminder channel: in-app notification and/or SMS.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Activity" ADD COLUMN IF NOT EXISTS "reminderNotifyApp" BOOLEAN NOT NULL DEFAULT true;
|
||||||
|
ALTER TABLE "Activity" ADD COLUMN IF NOT EXISTS "reminderNotifySms" BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -368,6 +368,10 @@ model Activity {
|
|||||||
dueAt DateTime?
|
dueAt DateTime?
|
||||||
durationMinutes Int?
|
durationMinutes Int?
|
||||||
reminderAt DateTime?
|
reminderAt DateTime?
|
||||||
|
/// اعلان داخل پنل هنگام رسیدن یادآور
|
||||||
|
reminderNotifyApp Boolean @default(true)
|
||||||
|
/// پیامک به موبایل مسئول هنگام رسیدن یادآور
|
||||||
|
reminderNotifySms Boolean @default(false)
|
||||||
completedAt DateTime?
|
completedAt DateTime?
|
||||||
personId String?
|
personId String?
|
||||||
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ type ActivityRow = {
|
|||||||
dueAt: Date | null;
|
dueAt: Date | null;
|
||||||
durationMinutes: number | null;
|
durationMinutes: number | null;
|
||||||
reminderAt: Date | null;
|
reminderAt: Date | null;
|
||||||
|
reminderNotifyApp: boolean;
|
||||||
|
reminderNotifySms: boolean;
|
||||||
completedAt: Date | null;
|
completedAt: Date | null;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
@@ -183,6 +185,8 @@ export class ActivitiesService {
|
|||||||
dueAt: dto.dueAt ? new Date(dto.dueAt) : null,
|
dueAt: dto.dueAt ? new Date(dto.dueAt) : null,
|
||||||
durationMinutes: dto.durationMinutes ?? null,
|
durationMinutes: dto.durationMinutes ?? null,
|
||||||
reminderAt: dto.reminderAt ? new Date(dto.reminderAt) : null,
|
reminderAt: dto.reminderAt ? new Date(dto.reminderAt) : null,
|
||||||
|
reminderNotifyApp: dto.reminderNotifyApp ?? true,
|
||||||
|
reminderNotifySms: dto.reminderNotifySms ?? false,
|
||||||
personId: emptyToNull(dto.personId ?? null),
|
personId: emptyToNull(dto.personId ?? null),
|
||||||
companyId: emptyToNull(dto.companyId ?? null),
|
companyId: emptyToNull(dto.companyId ?? null),
|
||||||
dealId: emptyToNull(dto.dealId ?? null),
|
dealId: emptyToNull(dto.dealId ?? null),
|
||||||
@@ -225,6 +229,8 @@ export class ActivitiesService {
|
|||||||
if ('dueAt' in dto) data.dueAt = dto.dueAt ? new Date(dto.dueAt) : null;
|
if ('dueAt' in dto) data.dueAt = dto.dueAt ? new Date(dto.dueAt) : null;
|
||||||
if ('durationMinutes' in dto) data.durationMinutes = dto.durationMinutes ?? null;
|
if ('durationMinutes' in dto) data.durationMinutes = dto.durationMinutes ?? null;
|
||||||
if ('reminderAt' in dto) data.reminderAt = dto.reminderAt ? new Date(dto.reminderAt) : null;
|
if ('reminderAt' in dto) data.reminderAt = dto.reminderAt ? new Date(dto.reminderAt) : null;
|
||||||
|
if (dto.reminderNotifyApp !== undefined) data.reminderNotifyApp = dto.reminderNotifyApp;
|
||||||
|
if (dto.reminderNotifySms !== undefined) data.reminderNotifySms = dto.reminderNotifySms;
|
||||||
if ('personId' in dto) data.personId = emptyToNull(dto.personId ?? null);
|
if ('personId' in dto) data.personId = emptyToNull(dto.personId ?? null);
|
||||||
if ('companyId' in dto) data.companyId = emptyToNull(dto.companyId ?? null);
|
if ('companyId' in dto) data.companyId = emptyToNull(dto.companyId ?? null);
|
||||||
if ('dealId' in dto) data.dealId = emptyToNull(dto.dealId ?? null);
|
if ('dealId' in dto) data.dealId = emptyToNull(dto.dealId ?? null);
|
||||||
@@ -412,6 +418,8 @@ export class ActivitiesService {
|
|||||||
dueAt: row.dueAt?.toISOString() ?? null,
|
dueAt: row.dueAt?.toISOString() ?? null,
|
||||||
durationMinutes: row.durationMinutes,
|
durationMinutes: row.durationMinutes,
|
||||||
reminderAt: row.reminderAt?.toISOString() ?? null,
|
reminderAt: row.reminderAt?.toISOString() ?? null,
|
||||||
|
reminderNotifyApp: row.reminderNotifyApp,
|
||||||
|
reminderNotifySms: row.reminderNotifySms,
|
||||||
completedAt: row.completedAt?.toISOString() ?? null,
|
completedAt: row.completedAt?.toISOString() ?? null,
|
||||||
person: row.person
|
person: row.person
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
|
IsBoolean,
|
||||||
IsDateString,
|
IsDateString,
|
||||||
IsIn,
|
IsIn,
|
||||||
IsInt,
|
IsInt,
|
||||||
@@ -98,6 +99,14 @@ export class CreateActivityDto {
|
|||||||
@IsDateString()
|
@IsDateString()
|
||||||
reminderAt?: string | null;
|
reminderAt?: string | null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
reminderNotifyApp?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
reminderNotifySms?: boolean;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ValidateIf((_, v) => v !== '' && v != null)
|
@ValidateIf((_, v) => v !== '' && v != null)
|
||||||
@IsString()
|
@IsString()
|
||||||
@@ -160,6 +169,14 @@ export class UpdateActivityDto {
|
|||||||
@IsDateString()
|
@IsDateString()
|
||||||
reminderAt?: string | null;
|
reminderAt?: string | null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
reminderNotifyApp?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
reminderNotifySms?: boolean;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ValidateIf((_, v) => v !== '' && v != null)
|
@ValidateIf((_, v) => v !== '' && v != null)
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { NotificationsController } from './notifications.controller';
|
import { NotificationsController } from './notifications.controller';
|
||||||
import { NotificationsService } from './notifications.service';
|
import { NotificationsService } from './notifications.service';
|
||||||
|
import { SmsService } from '../common/sms.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [NotificationsController],
|
controllers: [NotificationsController],
|
||||||
providers: [NotificationsService],
|
providers: [NotificationsService, SmsService],
|
||||||
exports: [NotificationsService],
|
exports: [NotificationsService],
|
||||||
})
|
})
|
||||||
export class NotificationsModule {}
|
export class NotificationsModule {}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import type { NotificationDto, NotificationsListDto } from '@mento/shared';
|
import type { NotificationDto, NotificationsListDto } from '@mento/shared';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { SmsService } from '../common/sms.service';
|
||||||
|
|
||||||
export type CreateNotificationInput = {
|
export type CreateNotificationInput = {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
@@ -17,7 +18,12 @@ export type CreateNotificationInput = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NotificationsService {
|
export class NotificationsService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
private readonly logger = new Logger(NotificationsService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly sms: SmsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async list(
|
async list(
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
@@ -55,16 +61,16 @@ export class NotificationsService {
|
|||||||
userId: string,
|
userId: string,
|
||||||
id: string,
|
id: string,
|
||||||
): Promise<NotificationDto | null> {
|
): Promise<NotificationDto | null> {
|
||||||
const existing = await this.prisma.notification.findFirst({
|
const row = await this.prisma.notification.findFirst({
|
||||||
where: { id, organizationId, userId },
|
where: { id, organizationId, userId },
|
||||||
});
|
});
|
||||||
if (!existing) return null;
|
if (!row) return null;
|
||||||
if (existing.readAt) return this.toDto(existing);
|
if (row.readAt) return this.toDto(row);
|
||||||
const row = await this.prisma.notification.update({
|
const updated = await this.prisma.notification.update({
|
||||||
where: { id },
|
where: { id: row.id },
|
||||||
data: { readAt: new Date() },
|
data: { readAt: new Date() },
|
||||||
});
|
});
|
||||||
return this.toDto(row);
|
return this.toDto(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async markAllRead(organizationId: string, userId: string): Promise<number> {
|
async markAllRead(organizationId: string, userId: string): Promise<number> {
|
||||||
@@ -136,6 +142,51 @@ export class NotificationsService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dueActivities = await this.prisma.activity.findMany({
|
||||||
|
where: {
|
||||||
|
organizationId,
|
||||||
|
assigneeId: userId,
|
||||||
|
status: 'open',
|
||||||
|
reminderAt: { lte: now },
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
reminderNotifyApp: true,
|
||||||
|
reminderNotifySms: true,
|
||||||
|
assignee: { select: { mobile: true, fullName: true } },
|
||||||
|
},
|
||||||
|
take: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const activity of dueActivities) {
|
||||||
|
if (activity.reminderNotifyApp) {
|
||||||
|
await this.create({
|
||||||
|
organizationId,
|
||||||
|
userId,
|
||||||
|
type: 'activity_reminder',
|
||||||
|
title: 'یادآوری فعالیت',
|
||||||
|
body: activity.title,
|
||||||
|
link: `/activities/${activity.id}`,
|
||||||
|
entityType: 'activity',
|
||||||
|
entityId: activity.id,
|
||||||
|
dedupe: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity.reminderNotifySms) {
|
||||||
|
await this.sendActivityReminderSms(
|
||||||
|
activity.assignee?.mobile,
|
||||||
|
activity.title,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.activity.update({
|
||||||
|
where: { id: activity.id },
|
||||||
|
data: { reminderAt: null },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const dueNotes = await this.prisma.note.findMany({
|
const dueNotes = await this.prisma.note.findMany({
|
||||||
where: {
|
where: {
|
||||||
organizationId,
|
organizationId,
|
||||||
@@ -158,7 +209,6 @@ export class NotificationsService {
|
|||||||
entityId: note.id,
|
entityId: note.id,
|
||||||
dedupe: true,
|
dedupe: true,
|
||||||
});
|
});
|
||||||
// یادآور فقط یکبار شلیک میشود؛ تکرار بعد از خواندن نباید رخ دهد
|
|
||||||
await this.prisma.note.update({
|
await this.prisma.note.update({
|
||||||
where: { id: note.id },
|
where: { id: note.id },
|
||||||
data: { reminderAt: null },
|
data: { reminderAt: null },
|
||||||
@@ -166,6 +216,25 @@ export class NotificationsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async sendActivityReminderSms(
|
||||||
|
mobile: string | null | undefined,
|
||||||
|
title: string,
|
||||||
|
) {
|
||||||
|
if (!mobile) {
|
||||||
|
this.logger.warn('Activity reminder SMS skipped: assignee has no mobile');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.sms.isConfigured()) {
|
||||||
|
this.logger.warn('Activity reminder SMS skipped: SMS not configured');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = `یادآوری منتو: ${title}`.slice(0, 160);
|
||||||
|
const result = await this.sms.sendSmsDetailed(mobile, text);
|
||||||
|
if (!result.ok) {
|
||||||
|
this.logger.warn(`Activity reminder SMS failed: ${result.reason ?? 'unknown'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Keep newest notification per type+entity; drop repeated sync spam. */
|
/** Keep newest notification per type+entity; drop repeated sync spam. */
|
||||||
private async collapseDuplicates(userId: string): Promise<void> {
|
private async collapseDuplicates(userId: string): Promise<void> {
|
||||||
const rows = await this.prisma.notification.findMany({
|
const rows = await this.prisma.notification.findMany({
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ const assigneeId = ref('');
|
|||||||
const isDone = ref(false);
|
const isDone = ref(false);
|
||||||
const needsNote = ref(false);
|
const needsNote = ref(false);
|
||||||
const reminderAt = ref('');
|
const reminderAt = ref('');
|
||||||
|
const reminderNotifyApp = ref(true);
|
||||||
|
const reminderNotifySms = ref(false);
|
||||||
|
|
||||||
const typeOpen = ref(false);
|
const typeOpen = ref(false);
|
||||||
const assigneeOpen = ref(false);
|
const assigneeOpen = ref(false);
|
||||||
@@ -136,6 +138,8 @@ function resetForm() {
|
|||||||
isDone.value = false;
|
isDone.value = false;
|
||||||
needsNote.value = false;
|
needsNote.value = false;
|
||||||
reminderAt.value = '';
|
reminderAt.value = '';
|
||||||
|
reminderNotifyApp.value = true;
|
||||||
|
reminderNotifySms.value = false;
|
||||||
tab.value = 'main';
|
tab.value = 'main';
|
||||||
error.value = '';
|
error.value = '';
|
||||||
}
|
}
|
||||||
@@ -177,6 +181,8 @@ async function load() {
|
|||||||
reminderAt.value = data.reminderAt
|
reminderAt.value = data.reminderAt
|
||||||
? formatLocalDateTimeInput(new Date(data.reminderAt))
|
? formatLocalDateTimeInput(new Date(data.reminderAt))
|
||||||
: '';
|
: '';
|
||||||
|
reminderNotifyApp.value = data.reminderNotifyApp ?? true;
|
||||||
|
reminderNotifySms.value = data.reminderNotifySms ?? false;
|
||||||
personId.value = data.person?.id ?? '';
|
personId.value = data.person?.id ?? '';
|
||||||
dealId.value = data.deal?.id ?? '';
|
dealId.value = data.deal?.id ?? '';
|
||||||
assigneeId.value = data.assignee?.id ?? auth.user?.id ?? '';
|
assigneeId.value = data.assignee?.id ?? auth.user?.id ?? '';
|
||||||
@@ -218,6 +224,8 @@ function payload(): CreateActivityRequest {
|
|||||||
dueAt: due && !Number.isNaN(due.getTime()) ? due.toISOString() : null,
|
dueAt: due && !Number.isNaN(due.getTime()) ? due.toISOString() : null,
|
||||||
durationMinutes: durationMinutes.value || null,
|
durationMinutes: durationMinutes.value || null,
|
||||||
reminderAt: reminder && !Number.isNaN(reminder.getTime()) ? reminder.toISOString() : null,
|
reminderAt: reminder && !Number.isNaN(reminder.getTime()) ? reminder.toISOString() : null,
|
||||||
|
reminderNotifyApp: reminder ? reminderNotifyApp.value : false,
|
||||||
|
reminderNotifySms: reminder ? reminderNotifySms.value : false,
|
||||||
personId: personId.value || null,
|
personId: personId.value || null,
|
||||||
companyId: selectedPerson.value?.company?.id ?? null,
|
companyId: selectedPerson.value?.company?.id ?? null,
|
||||||
dealId: dealId.value || null,
|
dealId: dealId.value || null,
|
||||||
@@ -232,6 +240,9 @@ function validate() {
|
|||||||
if (!assigneeId.value) return 'مسئول فعالیت را انتخاب کنید.';
|
if (!assigneeId.value) return 'مسئول فعالیت را انتخاب کنید.';
|
||||||
if (isNew.value && !personId.value) return 'شخص یا مشتری مرتبط را انتخاب کنید.';
|
if (isNew.value && !personId.value) return 'شخص یا مشتری مرتبط را انتخاب کنید.';
|
||||||
if (needsNote.value && !description.value.trim()) return 'توضیح این فعالیت را بنویسید.';
|
if (needsNote.value && !description.value.trim()) return 'توضیح این فعالیت را بنویسید.';
|
||||||
|
if (reminderAt.value && !reminderNotifyApp.value && !reminderNotifySms.value) {
|
||||||
|
return 'برای یادآوری، اعلان یا پیامک را انتخاب کنید.';
|
||||||
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,6 +492,29 @@ watch([id, isNew], () => void load(), { immediate: true });
|
|||||||
icon="bell"
|
icon="bell"
|
||||||
placeholder="یادآوری تنظیم نشده است"
|
placeholder="یادآوری تنظیم نشده است"
|
||||||
/>
|
/>
|
||||||
|
<div v-if="reminderAt" class="reminder-channels">
|
||||||
|
<p class="channel-title">چگونه یادآوری شود؟</p>
|
||||||
|
<label class="channel">
|
||||||
|
<input v-model="reminderNotifyApp" type="checkbox" />
|
||||||
|
<span>
|
||||||
|
<strong>اعلان داخل پنل</strong>
|
||||||
|
<small>در زنگوله اعلانهای منتو نشان داده میشود</small>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="channel">
|
||||||
|
<input v-model="reminderNotifySms" type="checkbox" />
|
||||||
|
<span>
|
||||||
|
<strong>پیامک به مسئول</strong>
|
||||||
|
<small>به شماره موبایل مسئول فعالیت ارسال میشود</small>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<p
|
||||||
|
v-if="reminderAt && !reminderNotifyApp && !reminderNotifySms"
|
||||||
|
class="channel-warn"
|
||||||
|
>
|
||||||
|
حداقل یکی از روشهای یادآوری را انتخاب کنید.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -914,6 +948,65 @@ watch([id, isNew], () => void load(), { immediate: true });
|
|||||||
padding-inline: 0.15rem;
|
padding-inline: 0.15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reminder-channels {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.55rem;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
border-top: 1px dashed #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.55rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.55rem 0.65rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel:has(input:checked) {
|
||||||
|
border-color: #99f6e4;
|
||||||
|
background: #f0fdfa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel input {
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
accent-color: #0d9488;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel span {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.15rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel strong {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel small {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-warn {
|
||||||
|
margin: 0;
|
||||||
|
color: #b45309;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.note-card {
|
.note-card {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.45rem;
|
gap: 0.45rem;
|
||||||
|
|||||||
@@ -489,6 +489,8 @@ export interface ActivityDto {
|
|||||||
dueAt: string | null;
|
dueAt: string | null;
|
||||||
durationMinutes: number | null;
|
durationMinutes: number | null;
|
||||||
reminderAt: string | null;
|
reminderAt: string | null;
|
||||||
|
reminderNotifyApp: boolean;
|
||||||
|
reminderNotifySms: boolean;
|
||||||
completedAt: string | null;
|
completedAt: string | null;
|
||||||
person: PersonSummaryDto | null;
|
person: PersonSummaryDto | null;
|
||||||
company: CompanySummaryDto | null;
|
company: CompanySummaryDto | null;
|
||||||
@@ -507,6 +509,8 @@ export interface CreateActivityRequest {
|
|||||||
dueAt?: string | null;
|
dueAt?: string | null;
|
||||||
durationMinutes?: number | null;
|
durationMinutes?: number | null;
|
||||||
reminderAt?: string | null;
|
reminderAt?: string | null;
|
||||||
|
reminderNotifyApp?: boolean;
|
||||||
|
reminderNotifySms?: boolean;
|
||||||
personId?: string | null;
|
personId?: string | null;
|
||||||
companyId?: string | null;
|
companyId?: string | null;
|
||||||
dealId?: string | null;
|
dealId?: string | null;
|
||||||
@@ -522,6 +526,8 @@ export interface UpdateActivityRequest {
|
|||||||
dueAt?: string | null;
|
dueAt?: string | null;
|
||||||
durationMinutes?: number | null;
|
durationMinutes?: number | null;
|
||||||
reminderAt?: string | null;
|
reminderAt?: string | null;
|
||||||
|
reminderNotifyApp?: boolean;
|
||||||
|
reminderNotifySms?: boolean;
|
||||||
personId?: string | null;
|
personId?: string | null;
|
||||||
companyId?: string | null;
|
companyId?: string | null;
|
||||||
dealId?: string | null;
|
dealId?: string | null;
|
||||||
@@ -741,6 +747,7 @@ export interface DealLineItemInput {
|
|||||||
export type NotificationType =
|
export type NotificationType =
|
||||||
| "activity_assigned"
|
| "activity_assigned"
|
||||||
| "activity_overdue"
|
| "activity_overdue"
|
||||||
|
| "activity_reminder"
|
||||||
| "deal_moved"
|
| "deal_moved"
|
||||||
| "note_reminder"
|
| "note_reminder"
|
||||||
| "ticket_assigned"
|
| "ticket_assigned"
|
||||||
|
|||||||
Reference in New Issue
Block a user