Files
teatea-pension/modules/family/types.ts

247 lines
6.8 KiB
TypeScript

export const FAMILY_CONTACT_STATUS_VALUES = ["active", "suspended", "archived"] as const;
export type FamilyContactStatus = (typeof FAMILY_CONTACT_STATUS_VALUES)[number];
export const FAMILY_VISIT_STATUS_VALUES = ["requested", "approved", "completed", "cancelled"] as const;
export type FamilyVisitStatus = (typeof FAMILY_VISIT_STATUS_VALUES)[number];
export const FAMILY_FEEDBACK_TYPE_VALUES = ["service", "care", "meal", "environment", "other"] as const;
export type FamilyFeedbackType = (typeof FAMILY_FEEDBACK_TYPE_VALUES)[number];
export const FAMILY_FEEDBACK_STATUS_VALUES = ["open", "in_progress", "resolved", "closed"] as const;
export type FamilyFeedbackStatus = (typeof FAMILY_FEEDBACK_STATUS_VALUES)[number];
export const FAMILY_CONTACT_STATUS_LABELS: Record<FamilyContactStatus, string> = {
active: "有效",
suspended: "暂停",
archived: "归档",
};
export const FAMILY_VISIT_STATUS_LABELS: Record<FamilyVisitStatus, string> = {
requested: "待确认",
approved: "已确认",
completed: "已完成",
cancelled: "已取消",
};
export const FAMILY_FEEDBACK_TYPE_LABELS: Record<FamilyFeedbackType, string> = {
service: "服务",
care: "照护",
meal: "餐饮",
environment: "环境",
other: "其他",
};
export const FAMILY_FEEDBACK_STATUS_LABELS: Record<FamilyFeedbackStatus, string> = {
open: "待处理",
in_progress: "处理中",
resolved: "已解决",
closed: "已关闭",
};
export type FamilyContact = {
id: string;
organizationId: string;
elderId: string;
elderName: string;
name: string;
relationship: string;
phone: string;
status: FamilyContactStatus;
notes: string;
createdAt: string;
updatedAt: string;
};
export type FamilyVisitAppointment = {
id: string;
organizationId: string;
elderId: string;
elderName: string;
contactId?: string;
contactName: string;
scheduledAt: string;
status: FamilyVisitStatus;
notes: string;
handledByAccountId?: string;
handledByName?: string;
handledAt?: string;
createdAt: string;
updatedAt: string;
};
export type FamilyFeedback = {
id: string;
organizationId: string;
elderId: string;
elderName: string;
contactId?: string;
contactName: string;
feedbackType: FamilyFeedbackType;
content: string;
status: FamilyFeedbackStatus;
responseNotes: string;
handledByAccountId?: string;
handledByName?: string;
handledAt?: string;
createdAt: string;
updatedAt: string;
};
export type FamilyServiceData = {
metrics: {
activeContacts: number;
pendingVisits: number;
openFeedback: number;
resolvedFeedback: number;
};
elderOptions: Array<{ id: string; name: string }>;
contacts: FamilyContact[];
visits: FamilyVisitAppointment[];
feedback: FamilyFeedback[];
};
export type FamilyContactInput = {
elderId: string;
name: string;
relationship: string;
phone: string;
status: FamilyContactStatus;
notes: string;
};
export type FamilyVisitInput = {
elderId: string;
contactId?: string;
scheduledAt: string;
status: FamilyVisitStatus;
notes: string;
};
export type FamilyFeedbackInput = {
elderId: string;
contactId?: string;
feedbackType: FamilyFeedbackType;
content: string;
status: FamilyFeedbackStatus;
responseNotes: string;
};
type ValidationSuccess<T> = {
success: true;
data: T;
};
type ValidationFailure = {
success: false;
reason: string;
};
export type ValidationResult<T> = ValidationSuccess<T> | ValidationFailure;
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function readString(source: Record<string, unknown>, key: string): string {
const value = source[key];
return typeof value === "string" ? value.trim() : "";
}
function readOptionalString(source: Record<string, unknown>, key: string): string | undefined {
const value = readString(source, key);
return value || undefined;
}
function readEnum<T extends string>(value: unknown, values: readonly T[]): T | null {
return typeof value === "string" ? values.find((item) => item === value) ?? null : null;
}
function validateRequiredDate(value: string, label: string): ValidationResult<string> {
return value && !Number.isNaN(new Date(value).getTime())
? { success: true, data: value }
: { success: false, reason: `${label}无效` };
}
export function validateFamilyContactInput(value: unknown): ValidationResult<FamilyContactInput> {
if (!isRecord(value)) {
return { success: false, reason: "请求数据格式无效" };
}
const elderId = readString(value, "elderId");
const name = readString(value, "name");
const relationship = readString(value, "relationship");
const phone = readString(value, "phone");
if (!elderId || !name || !relationship || !phone) {
return { success: false, reason: "老人、家属姓名、关系和手机号不能为空" };
}
const status = readEnum(value.status, FAMILY_CONTACT_STATUS_VALUES);
if (!status) {
return { success: false, reason: "家属联系人状态无效" };
}
return { success: true, data: { elderId, name, relationship, phone, status, notes: readString(value, "notes") } };
}
export function validateFamilyVisitInput(value: unknown): ValidationResult<FamilyVisitInput> {
if (!isRecord(value)) {
return { success: false, reason: "请求数据格式无效" };
}
const elderId = readString(value, "elderId");
if (!elderId) {
return { success: false, reason: "探访预约必须选择老人" };
}
const scheduledAt = validateRequiredDate(readString(value, "scheduledAt"), "预约时间");
if (!scheduledAt.success) {
return scheduledAt;
}
const status = readEnum(value.status, FAMILY_VISIT_STATUS_VALUES);
if (!status) {
return { success: false, reason: "探访预约状态无效" };
}
return {
success: true,
data: {
elderId,
contactId: readOptionalString(value, "contactId"),
scheduledAt: scheduledAt.data,
status,
notes: readString(value, "notes"),
},
};
}
export function validateFamilyFeedbackInput(value: unknown): ValidationResult<FamilyFeedbackInput> {
if (!isRecord(value)) {
return { success: false, reason: "请求数据格式无效" };
}
const elderId = readString(value, "elderId");
const content = readString(value, "content");
if (!elderId || !content) {
return { success: false, reason: "反馈必须选择老人并填写内容" };
}
const feedbackType = readEnum(value.feedbackType, FAMILY_FEEDBACK_TYPE_VALUES);
const status = readEnum(value.status, FAMILY_FEEDBACK_STATUS_VALUES);
if (!feedbackType || !status) {
return { success: false, reason: "反馈类型或状态无效" };
}
return {
success: true,
data: {
elderId,
contactId: readOptionalString(value, "contactId"),
feedbackType,
content,
status,
responseNotes: readString(value, "responseNotes"),
},
};
}