feat(domain): complete phase 2 backend core
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
calendarEventTypes,
|
||||
chatMessageRoles,
|
||||
clientActivityTypes,
|
||||
clientPipelineStages,
|
||||
clientStatuses,
|
||||
contractStatuses,
|
||||
financeTransactionTypes,
|
||||
invoiceStatuses,
|
||||
paymentStatuses,
|
||||
planningSectionCategories,
|
||||
projectProgressTypes,
|
||||
projectStatuses,
|
||||
projectTypes,
|
||||
proposalStatuses,
|
||||
revisionStatuses,
|
||||
subscriptionBillingCycles,
|
||||
subscriptionStatuses,
|
||||
taskPriorities,
|
||||
taskStatuses,
|
||||
} from "./types";
|
||||
import { DomainError } from "./errors";
|
||||
|
||||
export const resourceIdSchema = z.string().trim().min(1).max(128);
|
||||
export const businessDateSchema = z
|
||||
.string()
|
||||
.regex(/^\d{4}-\d{2}-\d{2}$/, "Tarih YYYY-MM-DD formatında olmalıdır.");
|
||||
export const currencySchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.length(3)
|
||||
.transform((value) => value.toUpperCase());
|
||||
export const minorAmountSchema = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
||||
|
||||
const optionalText = (max: number) => z.string().trim().max(max).nullable().optional();
|
||||
const optionalId = resourceIdSchema.nullable().optional();
|
||||
const optionalDate = businessDateSchema.nullable().optional();
|
||||
|
||||
export const clientCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
name: z.string().trim().min(1).max(160),
|
||||
companyName: optionalText(160),
|
||||
email: z.email().nullable().optional(),
|
||||
phone: optionalText(40),
|
||||
website: z.url().nullable().optional(),
|
||||
status: z.enum(clientStatuses).default("active"),
|
||||
pipelineStage: z.enum(clientPipelineStages).default("lead"),
|
||||
nextFollowUpDate: optionalDate,
|
||||
notes: optionalText(10_000),
|
||||
});
|
||||
export const clientUpdateSchema = clientCreateSchema.omit({ id: true }).partial();
|
||||
|
||||
export const clientActivityCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
clientId: resourceIdSchema,
|
||||
type: z.enum(clientActivityTypes),
|
||||
title: z.string().trim().min(1).max(200),
|
||||
content: optionalText(10_000),
|
||||
activityDate: z.date().default(() => new Date()),
|
||||
});
|
||||
|
||||
export const projectCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
clientId: optionalId,
|
||||
name: z.string().trim().min(1).max(200),
|
||||
type: z.enum(projectTypes).default("client_project"),
|
||||
description: optionalText(20_000),
|
||||
status: z.enum(projectStatuses).default("planning"),
|
||||
startDate: optionalDate,
|
||||
dueDate: optionalDate,
|
||||
budgetAmountMinor: minorAmountSchema.nullable().optional(),
|
||||
currency: currencySchema.default("USD"),
|
||||
progress: z.number().int().min(0).max(100).default(0),
|
||||
progressType: z.enum(projectProgressTypes).default("manual"),
|
||||
revisionQuota: z.number().int().min(0).max(10_000).default(0),
|
||||
legacyCoverImagePath: optionalText(1_000),
|
||||
coverImageAlt: optionalText(500),
|
||||
});
|
||||
export const projectUpdateSchema = projectCreateSchema.omit({ id: true }).partial();
|
||||
|
||||
export const taskCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
clientId: optionalId,
|
||||
projectId: optionalId,
|
||||
sourceJournalEntryId: optionalId,
|
||||
title: z.string().trim().min(1).max(300),
|
||||
description: optionalText(20_000),
|
||||
status: z.enum(taskStatuses).default("todo"),
|
||||
priority: z.enum(taskPriorities).default("medium"),
|
||||
scheduledDate: optionalDate,
|
||||
dueAt: z.date().nullable().optional(),
|
||||
estimatedMinutes: z.number().int().min(0).nullable().optional(),
|
||||
actualMinutes: z.number().int().min(0).nullable().optional(),
|
||||
aiGenerated: z.boolean().default(false),
|
||||
isPublicToClient: z.boolean().default(false),
|
||||
});
|
||||
export const taskUpdateSchema = taskCreateSchema.omit({ id: true }).partial();
|
||||
|
||||
const calendarEventBaseSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
clientId: optionalId,
|
||||
projectId: optionalId,
|
||||
taskId: optionalId,
|
||||
title: z.string().trim().min(1).max(300),
|
||||
description: optionalText(20_000),
|
||||
type: z.enum(calendarEventTypes).default("focus"),
|
||||
startsAt: z.date(),
|
||||
endsAt: z.date().nullable().optional(),
|
||||
});
|
||||
export const calendarEventCreateSchema = calendarEventBaseSchema
|
||||
.refine((value) => !value.endsAt || value.endsAt >= value.startsAt, {
|
||||
message: "Bitiş zamanı başlangıç zamanından önce olamaz.",
|
||||
path: ["endsAt"],
|
||||
});
|
||||
export const calendarEventUpdateSchema = calendarEventBaseSchema.omit({ id: true }).partial();
|
||||
|
||||
export const financeTransactionCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
clientId: optionalId,
|
||||
projectId: optionalId,
|
||||
type: z.enum(financeTransactionTypes),
|
||||
amountMinor: minorAmountSchema,
|
||||
currency: currencySchema.default("USD"),
|
||||
transactionDate: businessDateSchema,
|
||||
category: optionalText(160),
|
||||
paymentStatus: z.enum(paymentStatuses).default("planned"),
|
||||
description: optionalText(10_000),
|
||||
});
|
||||
export const financeTransactionUpdateSchema = financeTransactionCreateSchema.omit({ id: true }).partial();
|
||||
|
||||
export const journalEntrySchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
entryDate: businessDateSchema,
|
||||
moodScore: z.number().int().min(1).max(5).nullable().optional(),
|
||||
energyScore: z.number().int().min(1).max(5).nullable().optional(),
|
||||
workSatisfactionScore: z.number().int().min(1).max(5).nullable().optional(),
|
||||
moodLabel: optionalText(80),
|
||||
note: optionalText(30_000),
|
||||
legacyAiMetadata: z.record(z.string(), z.unknown()).nullable().optional(),
|
||||
});
|
||||
|
||||
export const planningSectionCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
projectId: resourceIdSchema,
|
||||
category: z.enum(planningSectionCategories),
|
||||
title: z.string().trim().min(1).max(300),
|
||||
content: optionalText(50_000),
|
||||
metadata: z.record(z.string(), z.unknown()).default({}),
|
||||
sortOrder: z.number().int().min(0).default(0),
|
||||
});
|
||||
export const planningSectionUpdateSchema = planningSectionCreateSchema.omit({ id: true, projectId: true }).partial();
|
||||
|
||||
export const revisionCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
projectId: resourceIdSchema,
|
||||
description: z.string().trim().min(1).max(20_000),
|
||||
});
|
||||
export const revisionStatusSchema = z.enum(revisionStatuses);
|
||||
|
||||
export const chatSessionCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
title: z.string().trim().min(1).max(300),
|
||||
});
|
||||
export const chatMessageCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
sessionId: resourceIdSchema,
|
||||
role: z.enum(chatMessageRoles),
|
||||
content: z.string().trim().min(1).max(100_000),
|
||||
contextJournalEntryIds: z.array(resourceIdSchema).max(100).default([]),
|
||||
});
|
||||
|
||||
export const proposalCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
clientId: optionalId,
|
||||
projectId: optionalId,
|
||||
title: z.string().trim().min(1).max(300),
|
||||
description: optionalText(30_000),
|
||||
amountMinor: minorAmountSchema.default(0),
|
||||
currency: currencySchema.default("TRY"),
|
||||
status: z.enum(proposalStatuses).default("draft"),
|
||||
validUntil: z.date().nullable().optional(),
|
||||
});
|
||||
export const contractCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
proposalId: optionalId,
|
||||
clientId: optionalId,
|
||||
title: z.string().trim().min(1).max(300),
|
||||
content: optionalText(100_000),
|
||||
status: z.enum(contractStatuses).default("draft"),
|
||||
signedAt: z.date().nullable().optional(),
|
||||
});
|
||||
export const invoiceCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
clientId: optionalId,
|
||||
projectId: optionalId,
|
||||
invoiceNumber: z.string().trim().min(1).max(100),
|
||||
amountMinor: minorAmountSchema.default(0),
|
||||
taxBasisPoints: z.number().int().min(0).max(10_000).default(0),
|
||||
currency: currencySchema.default("TRY"),
|
||||
status: z.enum(invoiceStatuses).default("draft"),
|
||||
issueDate: businessDateSchema,
|
||||
dueDate: optionalDate,
|
||||
paidAt: z.date().nullable().optional(),
|
||||
});
|
||||
export const subscriptionCreateSchema = z.object({
|
||||
id: resourceIdSchema.optional(),
|
||||
name: z.string().trim().min(1).max(300),
|
||||
amountMinor: minorAmountSchema.default(0),
|
||||
currency: currencySchema.default("TRY"),
|
||||
billingCycle: z.enum(subscriptionBillingCycles).default("monthly"),
|
||||
nextBillingDate: optionalDate,
|
||||
status: z.enum(subscriptionStatuses).default("active"),
|
||||
category: optionalText(160),
|
||||
});
|
||||
|
||||
export function parseDomainInput<TSchema extends z.ZodType>(
|
||||
schema: TSchema,
|
||||
input: unknown,
|
||||
): z.output<TSchema> {
|
||||
const result = schema.safeParse(input);
|
||||
|
||||
if (!result.success) {
|
||||
throw new DomainError("VALIDATION_ERROR", "Girilen bilgiler geçersiz.", {
|
||||
fields: result.error.flatten().fieldErrors,
|
||||
});
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
Reference in New Issue
Block a user