105 lines
3.3 KiB
Plaintext
105 lines
3.3 KiB
Plaintext
// P4 Tasks (AK-P4b) — lightweight cross-cutting task module.
|
|
//
|
|
// AD-21: one self-contained schema file per module. `tenantId` /
|
|
// `createdByUserId` / `assignedToUserId` / `assignedToGroupId` are intentionally
|
|
// BARE String scalars (no `@relation`) so this file has zero merge-conflict
|
|
// surface against `core.prisma`/`accounts.prisma` (no back-relation fields on
|
|
// Tenant/User/Group needed). Referential integrity is carried by app-level
|
|
// scoping (taskService + taskAccess) plus the RLS policies from the merge
|
|
// migration (see local/AK-P4b-merge-migration.sql).
|
|
//
|
|
// Assignment rule (DoD 2): a task is assigned to EXACTLY ONE of a user or a
|
|
// group; default = creator. Enforced in the service layer and by a DB CHECK
|
|
// constraint (num_nonnulls(...) = 1) added in the merge migration.
|
|
//
|
|
// Entity linking: the primary "this task is about X" association lives inline on
|
|
// the Task (`linkedEntityType`/`linkedEntityId`). The generic, reusable
|
|
// cross-entity link table is `EntityLink` (entityLinks.prisma, Gate W1); wiring
|
|
// tasks to concrete CRM entities happens in P6.
|
|
|
|
enum TaskStatus {
|
|
open
|
|
in_progress
|
|
blocked
|
|
done
|
|
cancelled
|
|
}
|
|
|
|
enum TaskPriority {
|
|
low
|
|
normal
|
|
high
|
|
urgent
|
|
}
|
|
|
|
enum TaskSource {
|
|
manual
|
|
chat
|
|
form_submission
|
|
automation
|
|
api
|
|
}
|
|
|
|
enum TaskEntityType {
|
|
contact
|
|
company
|
|
lead
|
|
deal
|
|
project
|
|
user
|
|
order
|
|
appointment
|
|
}
|
|
|
|
/// A task, scoped per tenant. Soft-delete via `archivedAt` (archive flag). A
|
|
/// completed task (`status = done`, `completedAt` set) is auto-archived 30 days
|
|
/// later by a maintenance cron.
|
|
model Task {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
tenantId String
|
|
createdByUserId String
|
|
title String
|
|
description String? // markdown
|
|
status TaskStatus @default(open)
|
|
priority TaskPriority @default(normal)
|
|
deadline DateTime?
|
|
// Assignment: EXACTLY ONE of user/group (DB CHECK in merge migration);
|
|
// default = creator (filled by the service when neither is provided).
|
|
assignedToUserId String?
|
|
assignedToGroupId String?
|
|
// Inline primary entity association (polymorphic) — "this task is about X".
|
|
linkedEntityType TaskEntityType?
|
|
linkedEntityId String?
|
|
source TaskSource @default(manual)
|
|
sourceRef String? // marker/link back to origin (chat msg / form submission / automation run)
|
|
completedAt DateTime?
|
|
isDemoData Boolean @default(false)
|
|
archivedAt DateTime?
|
|
|
|
watchers TaskWatcher[]
|
|
|
|
@@index([tenantId, archivedAt, status, deadline])
|
|
@@index([tenantId, assignedToUserId])
|
|
@@index([tenantId, assignedToGroupId])
|
|
@@index([tenantId, createdByUserId])
|
|
@@index([tenantId, linkedEntityType, linkedEntityId])
|
|
@@index([tenantId, status, completedAt]) // for the 30-day archive sweep
|
|
}
|
|
|
|
/// A user watching a task (M:N). Watchers receive reminder notifications in
|
|
/// addition to the assignee.
|
|
model TaskWatcher {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
tenantId String
|
|
taskId String
|
|
userId String
|
|
|
|
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([taskId, userId])
|
|
@@index([tenantId, userId])
|
|
}
|