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 = 'http://10.86.60.88:4000'
# VITE_API_BASE_URL = 'https://negareh-api.dev.danakcorp.com'
VITE_API_BASE_URL = 'http://10.164.213.88:4000'
VITE_TOKEN_NAME = 'negareh_at'
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 Select from "@/components/Select";
import { TickSquare } from "iconsax-react";
@@ -9,13 +9,15 @@ import { extractErrorMessage } from "@/config/func";
import { useFormik } from "formik";
import * as Yup from "yup";
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 InvoiceItemRow from "./components/InvoiceItemRow";
import { Checkbox } from "@/components/ui/checkbox";
import UploadBox from "@/components/UploadBox";
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 => ({
productId: "",
@@ -26,29 +28,33 @@ const createEmptyItem = (): CreatePreInvoiceItemType => ({
});
const CreateInvoice: FC = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const requestId = searchParams.get("requestId") ?? undefined;
const { mutate: submitInvoice, isPending } = useCreatePreInvoice();
const { mutate: submitInvoice, isPending } = useCreateInvoice();
const { data: usersData } = useGetUsers();
const { data: requestData } = useGetRequestDetail(requestId || "");
const multiUpload = useMultiUpload()
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: {
userId: "",
enableTax: false,
approvalDeadline: "",
paymentMethod: "",
notes: "",
},
validationSchema: Yup.object({
userId: Yup.string().required("انتخاب مشتری اجباری است."),
approvalDeadline: Yup.string().required("نحوه پرداخت اجباری است."),
userId: !requestId
? Yup.string().required("انتخاب مشتری اجباری است.")
: Yup.string(),
approvalDeadline: Yup.string().required("مهلت تایید اجباری است."),
}),
onSubmit: (values) => {
onSubmit: async (values) => {
const validItems = items.filter(
(i) => i.productId && i.quantity > 0 && i.unitPrice >= 0
);
@@ -58,9 +64,19 @@ const CreateInvoice: FC = () => {
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 = {
requestId,
userId: values.userId,
...(requestId ? { requestId } : { userId: values.userId }),
items: validItems.map((i) => ({
productId: i.productId,
quantity: i.quantity,
@@ -69,8 +85,10 @@ const CreateInvoice: FC = () => {
description: i.description || "",
})),
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,
...(attachments.length ? { attachments } : {}),
};
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 = (
index: number,
field: keyof CreatePreInvoiceItemType,
@@ -133,7 +145,7 @@ const CreateInvoice: FC = () => {
<Button
className="w-fit px-5"
onClick={() => formik.handleSubmit()}
isLoading={isPending}
isLoading={isPending || multiUpload.isPending}
>
<div className="flex gap-2 items-center">
<TickSquare size={20} color="black" />
@@ -145,22 +157,24 @@ const CreateInvoice: FC = () => {
<div className="bg-white rounded-3xl p-6 mt-8">
<div className="font-light">اطلاعات پیش فاکتور</div>
<div className="rowTwoInput mt-6">
<Select
items={users.map((u) => ({
label: u.firstName && u.lastName ? `${u.firstName} ${u.lastName}` : u.phone,
value: String(u.id),
}))}
label="مشتری"
placeholder="انتخاب"
value={formik.values.userId}
onChange={(e) => formik.setFieldValue("userId", e.target.value)}
onBlur={formik.handleBlur}
error_text={
formik.errors.userId && formik.touched.userId ? formik.errors.userId : ""
}
/>
</div>
{!requestId && (
<div className="rowTwoInput mt-6">
<Select
items={users.map((u) => ({
label: u.firstName && u.lastName ? `${u.firstName} ${u.lastName}` : u.phone,
value: String(u.id),
}))}
label="مشتری"
placeholder="انتخاب"
value={formik.values.userId}
onChange={(e) => formik.setFieldValue("userId", e.target.value)}
onBlur={formik.handleBlur}
error_text={
formik.errors.userId && formik.touched.userId ? formik.errors.userId : ""
}
/>
</div>
)}
<div className="mt-6">
{items.map((item, index) => (
@@ -192,13 +206,15 @@ const CreateInvoice: FC = () => {
</label>
</div>
<div className="mt-6">
<Textarea
label="نحوه پرداخت"
placeholder="مثال: مبلغ ۷۰ درصد پیش پرداخت / اعتبار ۲ روز"
value={formik.values.approvalDeadline}
onChange={(e) => formik.setFieldValue("approvalDeadline", e.target.value)}
onBlur={formik.handleBlur}
<div className="rowTwoInput mt-6">
<DatePickerComponent
label="مهلت تایید"
placeholder="انتخاب تاریخ"
defaultValue={formik.values.approvalDeadline || undefined}
onChange={(date) => {
formik.setFieldValue("approvalDeadline", date);
formik.setFieldTouched("approvalDeadline", true);
}}
error_text={
formik.errors.approvalDeadline && formik.touched.approvalDeadline
? formik.errors.approvalDeadline
@@ -207,6 +223,22 @@ const CreateInvoice: FC = () => {
/>
</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">
<Textarea
label="توضیحات"
@@ -219,7 +251,7 @@ const CreateInvoice: FC = () => {
</div>
<div className="mt-6">
<UploadBox label="فایل ضمیمه" onChange={setAttachmentFiles} />
<UploadBox isMultiple label="فایل ضمیمه" onChange={setAttachmentFiles} />
</div>
<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 * as api from "../service/InvoiceService";
import type { CreatePreInvoiceType } from "../types/Types";
export const useCreatePreInvoice = () => {
export const useCreateInvoice = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (params: CreatePreInvoiceType) => api.createPreInvoice(params),
mutationFn: api.createInvoice,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["orders-invoiced"] });
},
+2 -2
View File
@@ -1,7 +1,7 @@
import axios from "@/config/axios";
import type { CreatePreInvoiceType } from "../types/Types";
export const createPreInvoice = async (params: CreatePreInvoiceType) => {
const { data } = await axios.post("/admin/pre-invoice", params);
export const createInvoice = async (params: CreatePreInvoiceType) => {
const { data } = await axios.post("/admin/invoice", params);
return data;
};
+8 -1
View File
@@ -6,11 +6,18 @@ export type CreatePreInvoiceItemType = {
description: string;
};
export type InvoiceAttachmentType = {
type: string;
url: string;
};
export type CreatePreInvoiceType = {
requestId?: string;
userId: string;
userId?: string;
items: CreatePreInvoiceItemType[];
enableTax: boolean;
approvalDeadline: string;
paymentMethod?: string;
notes?: string;
attachments?: InvoiceAttachmentType[];
};