"use client"; import { useMemo, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { Plus } from "lucide-react"; import { accountingApi } from "@/lib/accounting-api"; import { useTenantId } from "@/hooks/useTenantId"; import { formatMoney, parseMoneyInput, toRialInteger } from "@/lib/utils"; import { PageHeader, Button, Dialog, Input, DataTable, EmptyState, LoadingState, ErrorState, FormField, MoneyInput, DatePicker, JalaliDateText, Select, Badge, } from "@/components/ds"; import { ItemCombobox } from "@/components/accounting/EntityCombobox"; type FormValues = { number: string; doc_date: string; reason: "expense" | "charity" | "consumption" | "barter"; item_label: string; item_id: string; qty: string; unit_cost: string; warehouse_id: string; debit_account_id: string; barter_debit_account_id: string; barter_amount: string; description: string; }; const REASON_FA: Record = { expense: "هزینه", charity: "خیرات / اهدایی", consumption: "مصرف داخلی (آبدارخانه و …)", barter: "تهاتر / معاوضه کالا", }; export default function InventoryIssuesPage() { const { tenantId } = useTenantId(); const qc = useQueryClient(); const [open, setOpen] = useState(false); const listQ = useQuery({ queryKey: ["accounting", tenantId, "ops-docs", "inventory", "issue"], queryFn: () => accountingApi.ops.listDocuments(tenantId!, "inventory", "issue"), enabled: !!tenantId, }); const accountsQ = useQuery({ queryKey: ["accounting", tenantId, "accounts"], queryFn: () => accountingApi.accounts.list(tenantId!), enabled: !!tenantId, }); const warehousesQ = useQuery({ queryKey: ["accounting", tenantId, "warehouses"], queryFn: () => accountingApi.ops.listWarehouses(tenantId!), enabled: !!tenantId, }); const policyQ = useQuery({ queryKey: ["accounting", tenantId, "posting-policy"], queryFn: () => accountingApi.setup.getPostingPolicy(tenantId!), enabled: !!tenantId, }); const form = useForm({ defaultValues: { number: "", doc_date: new Date().toISOString().slice(0, 10), reason: "expense", item_label: "", item_id: "", qty: "1", unit_cost: "0", warehouse_id: "", debit_account_id: "", barter_debit_account_id: "", barter_amount: "0", description: "", }, }); const reason = form.watch("reason"); const amount = useMemo(() => { const q = Number(toRialInteger(form.watch("qty")) || "0"); const c = Number(toRialInteger(form.watch("unit_cost")) || "0"); return String(q * c); }, [form.watch("qty"), form.watch("unit_cost")]); const postable = (accountsQ.data ?? []).filter((a) => a.is_postable); const autoIssue = Boolean(policyQ.data?.auto_post?.inventory_issue ?? true); const createM = useMutation({ mutationFn: async (d: FormValues) => { const amt = parseMoneyInput(amount) || "0"; if (Number(amt) <= 0) throw new Error("مبلغ خروج باید بزرگ‌تر از صفر باشد"); if (!d.debit_account_id) throw new Error("حساب هزینه/مقصد الزامی است"); if (d.reason === "barter" && !d.barter_debit_account_id) { throw new Error("برای تهاتر، حساب کالای دریافتی را انتخاب کنید"); } const doc = await accountingApi.ops.createDocument(tenantId!, { module: "inventory", doc_type: "issue", number: d.number?.trim() || undefined, doc_date: d.doc_date, amount: amt, description: d.description || REASON_FA[d.reason], status: "draft", meta_json: JSON.stringify({ reason: d.reason, item_id: d.item_id, item_label: d.item_label, qty: d.qty, unit_cost: d.unit_cost, warehouse_id: d.warehouse_id || undefined, debit_account_id: d.debit_account_id, barter_debit_account_id: d.barter_debit_account_id || undefined, barter_amount: d.barter_amount || undefined, }), }); if (autoIssue) { const posted = await accountingApi.purchaseInventory.postInventoryIssue(tenantId!, { source_document_id: String(doc.id), amount: amt, reason: d.reason, debit_account_id: d.debit_account_id, barter_debit_account_id: d.barter_debit_account_id || undefined, barter_amount: d.reason === "barter" ? parseMoneyInput(d.barter_amount) || amt : undefined, }); await accountingApi.ops.confirmDocument(tenantId!, String(doc.id)).catch(() => null); return posted; } return doc; }, onSuccess: async (res) => { const vn = res && typeof res === "object" && "voucher_number" in res ? String((res as { voucher_number?: string }).voucher_number || "") : ""; toast.success(vn ? `خروج ثبت و سند ${vn} صادر شد` : "خروج کالا ثبت شد"); setOpen(false); form.reset(); await qc.invalidateQueries({ queryKey: ["accounting", tenantId, "ops-docs", "inventory", "issue"] }); await qc.invalidateQueries({ queryKey: ["accounting", tenantId, "vouchers"] }); }, onError: (e: Error) => toast.error(e.message), }); if (!tenantId || listQ.isLoading) return ; if (listQ.error) return listQ.refetch()} />; return (
setOpen(true)}> خروج کالای جدید } /> , }, { key: "reason", header: "نوع", render: (r) => { try { const meta = JSON.parse(String(r.meta_json || "{}")) as { reason?: string }; return REASON_FA[meta.reason || ""] || meta.reason || "—"; } catch { return "—"; } }, }, { key: "amount", header: "مبلغ", render: (r) => formatMoney(String(r.amount ?? "0")), }, { key: "status", header: "وضعیت", render: (r) => {String(r.status)}, }, { key: "description", header: "شرح" }, ]} rows={(listQ.data ?? []) as unknown as Record[]} empty={} /> setOpen(false)} title="خروج / هزینه‌کرد کالا">
createM.mutate(d))}> ( )} />
{ form.setValue("item_id", item.id); form.setValue("item_label", `${item.code} — ${item.name}`); form.setValue("unit_cost", parseMoneyInput(item.unit_cost) || "0"); }} onClear={() => { form.setValue("item_id", ""); form.setValue("item_label", ""); }} />
{reason === "barter" ? ( <>
) : null}

{autoIssue ? "با ذخیره، سند حسابداری خودکار از Posting Engine صادر می‌شود." : "ثبت خودکار خاموش است — فقط سند عملیاتی ذخیره می‌شود (تنظیمات → اسناد اتوماتیک)."}

); }