create invoice

This commit is contained in:
hamid zarghami
2026-02-21 12:32:46 +03:30
parent 8a0a62b6d7
commit 369ad837ee
5 changed files with 90 additions and 52 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
VITE_API_BASE_URL = 'https://negareh-api.dev.danakcorp.com' # VITE_API_BASE_URL = 'https://negareh-api.dev.danakcorp.com'
# VITE_API_BASE_URL = 'http://10.86.60.88:4000' VITE_API_BASE_URL = 'http://10.164.213.88:4000'
VITE_TOKEN_NAME = 'negareh_at' VITE_TOKEN_NAME = 'negareh_at'
VITE_REFRESH_TOKEN_NAME = 'negareh_art' VITE_REFRESH_TOKEN_NAME = 'negareh_art'
+76 -44
View File
@@ -1,4 +1,4 @@
import { useState, useEffect, type FC } from "react"; import { useState, type FC } from "react";
import Button from "@/components/Button"; import Button from "@/components/Button";
import Select from "@/components/Select"; import Select from "@/components/Select";
import { TickSquare } from "iconsax-react"; import { TickSquare } from "iconsax-react";
@@ -9,13 +9,15 @@ import { extractErrorMessage } from "@/config/func";
import { useFormik } from "formik"; import { useFormik } from "formik";
import * as Yup from "yup"; import * as Yup from "yup";
import { useGetUsers } from "@/pages/user/hooks/useUserData"; import { useGetUsers } from "@/pages/user/hooks/useUserData";
import { useGetRequestDetail } from "@/pages/requests/hooks/useRequestData";
import { useCreatePreInvoice } from "./hooks/useInvoiceData";
import type { CreatePreInvoiceType, CreatePreInvoiceItemType } from "./types/Types"; import type { CreatePreInvoiceType, CreatePreInvoiceItemType } from "./types/Types";
import InvoiceItemRow from "./components/InvoiceItemRow"; import InvoiceItemRow from "./components/InvoiceItemRow";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import UploadBox from "@/components/UploadBox"; import UploadBox from "@/components/UploadBox";
import Textarea from "@/components/Textarea"; import Textarea from "@/components/Textarea";
import DatePickerComponent from "@/components/DatePicker";
import { useCreateInvoice } from "./hooks/useInvoiceData";
import { useMultiUpload } from "../uploader/hooks/useUploader";
import moment from "moment-jalaali";
const createEmptyItem = (): CreatePreInvoiceItemType => ({ const createEmptyItem = (): CreatePreInvoiceItemType => ({
productId: "", productId: "",
@@ -26,29 +28,33 @@ const createEmptyItem = (): CreatePreInvoiceItemType => ({
}); });
const CreateInvoice: FC = () => { const CreateInvoice: FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const requestId = searchParams.get("requestId") ?? undefined; const requestId = searchParams.get("requestId") ?? undefined;
const { mutate: submitInvoice, isPending } = useCreatePreInvoice(); const { mutate: submitInvoice, isPending } = useCreateInvoice();
const { data: usersData } = useGetUsers(); const { data: usersData } = useGetUsers();
const { data: requestData } = useGetRequestDetail(requestId || ""); const multiUpload = useMultiUpload()
const [items, setItems] = useState<CreatePreInvoiceItemType[]>([createEmptyItem()]); const [items, setItems] = useState<CreatePreInvoiceItemType[]>([createEmptyItem()]);
const [, setAttachmentFiles] = useState<File[]>([]); const [attachmentFiles, setAttachmentFiles] = useState<File[]>([]);
const formik = useFormik<Pick<CreatePreInvoiceType, "userId" | "enableTax" | "approvalDeadline" | "notes">>({ const formik = useFormik<Pick<CreatePreInvoiceType, "userId" | "enableTax" | "approvalDeadline" | "paymentMethod" | "notes">>({
initialValues: { initialValues: {
userId: "", userId: "",
enableTax: false, enableTax: false,
approvalDeadline: "", approvalDeadline: "",
paymentMethod: "",
notes: "", notes: "",
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
userId: Yup.string().required("انتخاب مشتری اجباری است."), userId: !requestId
approvalDeadline: Yup.string().required("نحوه پرداخت اجباری است."), ? Yup.string().required("انتخاب مشتری اجباری است.")
: Yup.string(),
approvalDeadline: Yup.string().required("مهلت تایید اجباری است."),
}), }),
onSubmit: (values) => { onSubmit: async (values) => {
const validItems = items.filter( const validItems = items.filter(
(i) => i.productId && i.quantity > 0 && i.unitPrice >= 0 (i) => i.productId && i.quantity > 0 && i.unitPrice >= 0
); );
@@ -58,9 +64,19 @@ const CreateInvoice: FC = () => {
return; return;
} }
const attachments: { type: string; url: string }[] = [];
if (attachmentFiles.length) {
const uploadResult = await multiUpload.mutateAsync(attachmentFiles);
uploadResult?.data?.forEach((item) => {
attachments.push({
type: "uploads_attach",
url: item.url,
});
});
}
const params: CreatePreInvoiceType = { const params: CreatePreInvoiceType = {
requestId, ...(requestId ? { requestId } : { userId: values.userId }),
userId: values.userId,
items: validItems.map((i) => ({ items: validItems.map((i) => ({
productId: i.productId, productId: i.productId,
quantity: i.quantity, quantity: i.quantity,
@@ -69,8 +85,10 @@ const CreateInvoice: FC = () => {
description: i.description || "", description: i.description || "",
})), })),
enableTax: values.enableTax, enableTax: values.enableTax,
approvalDeadline: values.approvalDeadline, approvalDeadline: moment(values.approvalDeadline, 'jYYYY/jMM/jDD').format('YYYY-MM-DD'),
...(values.paymentMethod ? { paymentMethod: values.paymentMethod } : {}),
notes: values.notes, notes: values.notes,
...(attachments.length ? { attachments } : {}),
}; };
submitInvoice(params, { submitInvoice(params, {
@@ -85,12 +103,6 @@ const CreateInvoice: FC = () => {
}, },
}); });
useEffect(() => {
if (requestId && requestData?.data?.user?.id) {
formik.setFieldValue("userId", String(requestData.data.user.id));
}
}, [requestId, requestData?.data?.user?.id]);
const handleItemChange = ( const handleItemChange = (
index: number, index: number,
field: keyof CreatePreInvoiceItemType, field: keyof CreatePreInvoiceItemType,
@@ -133,7 +145,7 @@ const CreateInvoice: FC = () => {
<Button <Button
className="w-fit px-5" className="w-fit px-5"
onClick={() => formik.handleSubmit()} onClick={() => formik.handleSubmit()}
isLoading={isPending} isLoading={isPending || multiUpload.isPending}
> >
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<TickSquare size={20} color="black" /> <TickSquare size={20} color="black" />
@@ -145,22 +157,24 @@ const CreateInvoice: FC = () => {
<div className="bg-white rounded-3xl p-6 mt-8"> <div className="bg-white rounded-3xl p-6 mt-8">
<div className="font-light">اطلاعات پیش فاکتور</div> <div className="font-light">اطلاعات پیش فاکتور</div>
<div className="rowTwoInput mt-6"> {!requestId && (
<Select <div className="rowTwoInput mt-6">
items={users.map((u) => ({ <Select
label: u.firstName && u.lastName ? `${u.firstName} ${u.lastName}` : u.phone, items={users.map((u) => ({
value: String(u.id), label: u.firstName && u.lastName ? `${u.firstName} ${u.lastName}` : u.phone,
}))} value: String(u.id),
label="مشتری" }))}
placeholder="انتخاب" label="مشتری"
value={formik.values.userId} placeholder="انتخاب"
onChange={(e) => formik.setFieldValue("userId", e.target.value)} value={formik.values.userId}
onBlur={formik.handleBlur} onChange={(e) => formik.setFieldValue("userId", e.target.value)}
error_text={ onBlur={formik.handleBlur}
formik.errors.userId && formik.touched.userId ? formik.errors.userId : "" error_text={
} formik.errors.userId && formik.touched.userId ? formik.errors.userId : ""
/> }
</div> />
</div>
)}
<div className="mt-6"> <div className="mt-6">
{items.map((item, index) => ( {items.map((item, index) => (
@@ -192,13 +206,15 @@ const CreateInvoice: FC = () => {
</label> </label>
</div> </div>
<div className="mt-6"> <div className="rowTwoInput mt-6">
<Textarea <DatePickerComponent
label="نحوه پرداخت" label="مهلت تایید"
placeholder="مثال: مبلغ ۷۰ درصد پیش پرداخت / اعتبار ۲ روز" placeholder="انتخاب تاریخ"
value={formik.values.approvalDeadline} defaultValue={formik.values.approvalDeadline || undefined}
onChange={(e) => formik.setFieldValue("approvalDeadline", e.target.value)} onChange={(date) => {
onBlur={formik.handleBlur} formik.setFieldValue("approvalDeadline", date);
formik.setFieldTouched("approvalDeadline", true);
}}
error_text={ error_text={
formik.errors.approvalDeadline && formik.touched.approvalDeadline formik.errors.approvalDeadline && formik.touched.approvalDeadline
? formik.errors.approvalDeadline ? formik.errors.approvalDeadline
@@ -207,6 +223,22 @@ const CreateInvoice: FC = () => {
/> />
</div> </div>
<div className="mt-6">
<Textarea
label="روش پرداخت"
name="paymentMethod"
placeholder="روش پرداخت"
value={formik.values.paymentMethod}
onChange={(e) => formik.setFieldValue("paymentMethod", e.target.value)}
onBlur={formik.handleBlur}
error_text={
formik.errors.paymentMethod && formik.touched.paymentMethod
? formik.errors.paymentMethod
: ""
}
/>
</div>
<div className="mt-6"> <div className="mt-6">
<Textarea <Textarea
label="توضیحات" label="توضیحات"
@@ -219,7 +251,7 @@ const CreateInvoice: FC = () => {
</div> </div>
<div className="mt-6"> <div className="mt-6">
<UploadBox label="فایل ضمیمه" onChange={setAttachmentFiles} /> <UploadBox isMultiple label="فایل ضمیمه" onChange={setAttachmentFiles} />
</div> </div>
<div className="mt-6 bg-[#F5F7FC] h-12 px-4 flex justify-between items-center rounded-xl"> <div className="mt-6 bg-[#F5F7FC] h-12 px-4 flex justify-between items-center rounded-xl">
+2 -3
View File
@@ -1,12 +1,11 @@
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import * as api from "../service/InvoiceService"; import * as api from "../service/InvoiceService";
import type { CreatePreInvoiceType } from "../types/Types";
export const useCreatePreInvoice = () => { export const useCreateInvoice = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: (params: CreatePreInvoiceType) => api.createPreInvoice(params), mutationFn: api.createInvoice,
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["orders-invoiced"] }); queryClient.invalidateQueries({ queryKey: ["orders-invoiced"] });
}, },
+2 -2
View File
@@ -1,7 +1,7 @@
import axios from "@/config/axios"; import axios from "@/config/axios";
import type { CreatePreInvoiceType } from "../types/Types"; import type { CreatePreInvoiceType } from "../types/Types";
export const createPreInvoice = async (params: CreatePreInvoiceType) => { export const createInvoice = async (params: CreatePreInvoiceType) => {
const { data } = await axios.post("/admin/pre-invoice", params); const { data } = await axios.post("/admin/invoice", params);
return data; return data;
}; };
+8 -1
View File
@@ -6,11 +6,18 @@ export type CreatePreInvoiceItemType = {
description: string; description: string;
}; };
export type InvoiceAttachmentType = {
type: string;
url: string;
};
export type CreatePreInvoiceType = { export type CreatePreInvoiceType = {
requestId?: string; requestId?: string;
userId: string; userId?: string;
items: CreatePreInvoiceItemType[]; items: CreatePreInvoiceItemType[];
enableTax: boolean; enableTax: boolean;
approvalDeadline: string; approvalDeadline: string;
paymentMethod?: string;
notes?: string; notes?: string;
attachments?: InvoiceAttachmentType[];
}; };