"use client"; import { Controller, useFieldArray, useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery } from "@tanstack/react-query"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { Plus, Trash2 } from "lucide-react"; import { accountingApi } from "@/lib/accounting-api"; import { useTenantId } from "@/hooks/useTenantId"; import { todayIso } from "@/lib/jalali"; import { PageHeader, Button, Input, Label, FieldError, LoadingState, ErrorState, Card, CardContent, DatePicker, Select, MoneyInput, } from "@/components/ds"; const lineSchema = z.object({ account_id: z.string().uuid("حساب معتبر انتخاب کنید"), debit: z.string().min(1), credit: z.string().min(1), description: z.string().optional(), cost_center_id: z.string().optional(), project_id: z.string().optional(), }); const schema = z.object({ fiscal_period_id: z.string().uuid("دوره مالی الزامی است"), voucher_number: z.string().min(1, "شماره سند الزامی است"), voucher_date: z.string().min(1), description: z.string().optional(), lines: z.array(lineSchema).min(2, "حداقل دو ردیف لازم است"), }); type FormValues = z.infer; export default function NewVoucherPage() { const { tenantId } = useTenantId(); const router = useRouter(); const form = useForm({ resolver: zodResolver(schema), defaultValues: { fiscal_period_id: "", voucher_number: `V-${Date.now().toString().slice(-8)}`, voucher_date: todayIso(), description: "", lines: [ { account_id: "", debit: "0", credit: "0", description: "", cost_center_id: "", project_id: "" }, { account_id: "", debit: "0", credit: "0", description: "", cost_center_id: "", project_id: "" }, ], }, }); const { fields, append, remove } = useFieldArray({ control: form.control, name: "lines" }); const periodsQ = useQuery({ queryKey: ["accounting", tenantId, "fiscal-periods"], queryFn: () => accountingApi.fiscal.listPeriods(tenantId!), enabled: !!tenantId, }); const accountsQ = useQuery({ queryKey: ["accounting", tenantId, "accounts"], queryFn: () => accountingApi.accounts.list(tenantId!), enabled: !!tenantId, }); const costCentersQ = useQuery({ queryKey: ["accounting", tenantId, "cost-centers"], queryFn: () => accountingApi.costCenters.list(tenantId!), enabled: !!tenantId, }); const projectsQ = useQuery({ queryKey: ["accounting", tenantId, "projects"], queryFn: () => accountingApi.projects.list(tenantId!), enabled: !!tenantId, }); const createM = useMutation({ mutationFn: (data: FormValues) => accountingApi.vouchers.create(tenantId!, { fiscal_period_id: data.fiscal_period_id, voucher_number: data.voucher_number, voucher_date: data.voucher_date, description: data.description || undefined, source_module: "manual", lines: data.lines.map((l) => ({ account_id: l.account_id, debit: l.debit || "0", credit: l.credit || "0", description: l.description || undefined, cost_center_id: l.cost_center_id || null, project_id: l.project_id || null, })), }), onSuccess: (v) => { toast.success("سند ایجاد شد"); router.push(`/accounting/vouchers/${v.id}`); }, onError: (e: Error) => toast.error(e.message), }); if (!tenantId || periodsQ.isLoading || accountsQ.isLoading) return ; if (periodsQ.error || accountsQ.error) { return ( { void periodsQ.refetch(); void accountsQ.refetch(); }} /> ); } const postable = (accountsQ.data ?? []).filter((a) => a.is_postable); const activeCostCenters = (costCentersQ.data ?? []).filter((c) => c.is_active); const activeProjects = (projectsQ.data ?? []).filter((p) => p.is_active); const openPeriods = (periodsQ.data ?? []).filter((p) => p.status === "open" || p.status === "locked"); return (
createM.mutate(d))}>
{form.formState.errors.fiscal_period_id?.message} {!openPeriods.length ? (

ابتدا یک دوره مالی باز ایجاد کنید.

) : null}
{form.formState.errors.voucher_number?.message}
( )} /> {form.formState.errors.voucher_date?.message}

ردیف‌های سند

{fields.map((field, index) => (
))} {form.formState.errors.lines?.message || form.formState.errors.lines?.root?.message}
); }