task-ep.1.1

This commit is contained in:
phaichayon
2026-07-10 10:33:45 +07:00
parent f47fea5d6f
commit 9ae4f31f28
33 changed files with 9680 additions and 22 deletions

View File

@@ -1,60 +1,100 @@
import { PERMISSIONS } from '@/lib/auth/rbac';
export const CRM_ACTIVITY_ENTITY_TYPES = [
'customer',
'contact',
'lead',
'opportunity',
'quotation',
'customer',
'contact',
'purchase_order',
'service_report'
'internal'
] as const;
export const CRM_ACTIVITY_TYPES = [
'site_visit',
'follow_up',
'meeting',
'visit',
'phone_call',
'email_follow_up',
'quotation_follow_up',
'site_survey',
'requirement_discussion',
'presentation',
'internal_task',
'reminder_action',
'other'
] as const;
export const CRM_ACTIVITY_STATUSES = [
'planned',
'draft',
'scheduled',
'in_progress',
'completed',
'cancelled',
'skipped',
'overdue'
] as const;
export const CRM_ACTIVITY_PRIORITIES = ['low', 'medium', 'high', 'urgent'] as const;
export const CRM_ACTIVITY_STORED_STATUSES = [
'draft',
'scheduled',
'in_progress',
'completed',
'cancelled',
'skipped'
] as const;
export const CRM_ACTIVITY_PRIORITIES = ['low', 'normal', 'high', 'critical'] as const;
export type CrmActivityEntityType = (typeof CRM_ACTIVITY_ENTITY_TYPES)[number];
export type CrmActivityType = (typeof CRM_ACTIVITY_TYPES)[number];
export type CrmActivityStatus = (typeof CRM_ACTIVITY_STATUSES)[number];
export type CrmActivityPriority = (typeof CRM_ACTIVITY_PRIORITIES)[number];
export type CrmActivityStoredStatus = (typeof CRM_ACTIVITY_STORED_STATUSES)[number];
export interface CrmActivityRelatedRecord {
entityType: CrmActivityEntityType;
entityId: string;
}
export interface CrmActivityRecord {
id: string;
organizationId: string;
entityType: CrmActivityEntityType;
entityId: string;
primaryEntityType: CrmActivityEntityType;
primaryEntityId: string | null;
primaryRecordLabel: string | null;
relatedRecords: CrmActivityRelatedRecord[];
customerId: string | null;
contactId: string | null;
leadId: string | null;
opportunityId: string | null;
quotationId: string | null;
activityType: CrmActivityType;
subject: string;
description: string | null;
ownerId: string | null;
ownerId: string;
assignedToId: string | null;
dueDate: string | null;
dueTime: string | null;
scheduledStartAt: string | null;
scheduledEndAt: string | null;
dueAt: string | null;
completedAt: string | null;
status: Exclude<CrmActivityStatus, 'overdue'>;
cancelledAt: string | null;
skippedAt: string | null;
status: CrmActivityStoredStatus;
priority: CrmActivityPriority;
outcome: string | null;
outcomeSummary: string | null;
cancellationReason: string | null;
skipReason: string | null;
nextAction: string | null;
branchId: string | null;
productType: string | null;
isInternalOnly: boolean;
isPricingSensitive: boolean;
parentActivityId: string | null;
metadata: Record<string, unknown> | null;
createdBy: string;
updatedBy: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
}
export const CRM_ACTIVITY_PERMISSIONS = {
@@ -62,25 +102,52 @@ export const CRM_ACTIVITY_PERMISSIONS = {
create: PERMISSIONS.crmActivityCreate,
update: PERMISSIONS.crmActivityUpdate,
complete: PERMISSIONS.crmActivityComplete,
cancel: PERMISSIONS.crmActivityUpdate,
reassign: PERMISSIONS.crmActivityUpdate,
delete: PERMISSIONS.crmActivityDelete,
auditLogRead: PERMISSIONS.crmAuditLogRead
} as const;
function parseDate(value?: string | null): number | null {
if (!value) {
return null;
}
const parsed = Date.parse(value);
return Number.isNaN(parsed) ? null : parsed;
}
export function resolveCrmActivityStatus(input: {
status: Exclude<CrmActivityStatus, 'overdue'>;
dueDate?: string | null;
status: CrmActivityStoredStatus;
scheduledStartAt?: string | null;
dueAt?: string | null;
completedAt?: string | null;
cancelledAt?: string | null;
skippedAt?: string | null;
now?: Date;
}): CrmActivityStatus {
if (input.completedAt || input.status === 'completed' || input.status === 'cancelled') {
if (
input.completedAt ||
input.cancelledAt ||
input.skippedAt ||
input.status === 'completed' ||
input.status === 'cancelled' ||
input.status === 'skipped'
) {
return input.status;
}
if (!input.dueDate) {
const compareAt = parseDate(input.dueAt) ?? parseDate(input.scheduledStartAt);
if (compareAt === null) {
return input.status;
}
const today = (input.now ?? new Date()).toISOString().slice(0, 10);
return input.dueDate < today ? 'overdue' : input.status;
return compareAt < (input.now ?? new Date()).getTime() ? 'overdue' : input.status;
}
export function formatCrmActivityLabel(value: string): string {
return value
.split('_')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}
import { PERMISSIONS } from '@/lib/auth/rbac';