added updation invoice
This commit is contained in:
parent
528255c2bd
commit
1b3e090211
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { AppFormProvider, useAppForm } from "../../hooks/appHooks/useAppForm";
|
import { AppFormProvider, useAppForm } from "../../hooks/appHooks/useAppForm";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import {
|
import {
|
||||||
@ -9,9 +9,13 @@ import {
|
|||||||
import PurchasePartyDetails from "./PurchasePartyDetails";
|
import PurchasePartyDetails from "./PurchasePartyDetails";
|
||||||
import PurchaseTransportDetails from "./PurchaseTransportDetails";
|
import PurchaseTransportDetails from "./PurchaseTransportDetails";
|
||||||
import PurchasePaymentDetails from "./PurchasePaymentDetails";
|
import PurchasePaymentDetails from "./PurchasePaymentDetails";
|
||||||
import { useCreatePurchaseInvoice } from "../../hooks/usePurchase";
|
import {
|
||||||
|
useCreatePurchaseInvoice,
|
||||||
const ManagePurchase = ({ onClose }) => {
|
usePurchase,
|
||||||
|
useUpdatePurchaseInvoice,
|
||||||
|
} from "../../hooks/usePurchase";
|
||||||
|
const ManagePurchase = ({ onClose, purchaseId }) => {
|
||||||
|
const { data } = usePurchase(purchaseId);
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
const [completedTabs, setCompletedTabs] = useState([]);
|
const [completedTabs, setCompletedTabs] = useState([]);
|
||||||
|
|
||||||
@ -20,7 +24,7 @@ const ManagePurchase = ({ onClose }) => {
|
|||||||
name: "Party Details",
|
name: "Party Details",
|
||||||
icon: "bx bx-user bx-md",
|
icon: "bx bx-user bx-md",
|
||||||
subtitle: "Supplier & project information",
|
subtitle: "Supplier & project information",
|
||||||
component: <PurchasePartyDetails />,
|
component: <PurchasePartyDetails purchase={data} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invoice & Transport",
|
name: "Invoice & Transport",
|
||||||
@ -40,10 +44,25 @@ const ManagePurchase = ({ onClose }) => {
|
|||||||
resolver: zodResolver(PurchaseSchema),
|
resolver: zodResolver(PurchaseSchema),
|
||||||
defaultValues: defaultPurchaseValue,
|
defaultValues: defaultPurchaseValue,
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
|
shouldUnregister: false,
|
||||||
});
|
});
|
||||||
const {reset} = purchaseOrder;
|
|
||||||
|
|
||||||
const handleNext = async () => {
|
const {
|
||||||
|
reset,
|
||||||
|
formState: { errors },
|
||||||
|
} = purchaseOrder;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (purchaseId && data) {
|
||||||
|
reset(data);
|
||||||
|
setCompletedTabs([0, 1, 2]);
|
||||||
|
}
|
||||||
|
}, [purchaseId, data, reset]);
|
||||||
|
|
||||||
|
const handleNext = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
const currentStepFields = getStepFields(activeTab);
|
const currentStepFields = getStepFields(activeTab);
|
||||||
const valid = await purchaseOrder.trigger(currentStepFields);
|
const valid = await purchaseOrder.trigger(currentStepFields);
|
||||||
|
|
||||||
@ -53,18 +72,53 @@ const ManagePurchase = ({ onClose }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePrev = () => {
|
const handlePrev = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
setActiveTab((prev) => Math.max(prev - 1, 0));
|
setActiveTab((prev) => Math.max(prev - 1, 0));
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutate: CreateInvoice, isPending } = useCreatePurchaseInvoice(() => {
|
const { mutate: CreateInvoice, isPending } =
|
||||||
reset()
|
useCreatePurchaseInvoice(() => {
|
||||||
|
reset();
|
||||||
|
onClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: updatePurchase, isPending: isUpdating } =
|
||||||
|
useUpdatePurchaseInvoice(() => {
|
||||||
|
reset();
|
||||||
onClose();
|
onClose();
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = (formData) => {
|
const onSubmit = (formData) => {
|
||||||
let payload = formData;
|
if (activeTab !== 2) {
|
||||||
CreateInvoice(payload);
|
console.warn("Submit blocked - not on last step");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirtyFields = purchaseOrder.formState.dirtyFields;
|
||||||
|
|
||||||
|
if (purchaseId) {
|
||||||
|
const changedData = Object.keys(dirtyFields).reduce((acc, key) => {
|
||||||
|
if (dirtyFields[key]) {
|
||||||
|
acc.push({
|
||||||
|
operationType: 0,
|
||||||
|
path: `/${key}`,
|
||||||
|
op: "replace",
|
||||||
|
from: null,
|
||||||
|
value: formData[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
updatePurchase({
|
||||||
|
purchaseId,
|
||||||
|
payload: changedData,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
CreateInvoice(formData);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -96,7 +150,9 @@ const ManagePurchase = ({ onClose }) => {
|
|||||||
|
|
||||||
<span className="bs-stepper-label">
|
<span className="bs-stepper-label">
|
||||||
<span className="bs-stepper-title">{step.name}</span>
|
<span className="bs-stepper-title">{step.name}</span>
|
||||||
<span className="bs-stepper-subtitle">{step.subtitle}</span>
|
<span className="bs-stepper-subtitle">
|
||||||
|
{step.subtitle}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -112,7 +168,23 @@ const ManagePurchase = ({ onClose }) => {
|
|||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="bs-stepper-content py-2">
|
<div className="bs-stepper-content py-2">
|
||||||
<AppFormProvider {...purchaseOrder}>
|
<AppFormProvider {...purchaseOrder}>
|
||||||
<form onSubmit={purchaseOrder.handleSubmit(onSubmit)}>
|
<form
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && activeTab !== 2) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (activeTab !== 2) {
|
||||||
|
console.warn("BLOCKED SUBMIT on step:", activeTab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
purchaseOrder.handleSubmit(onSubmit)(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{stepsConfig[activeTab].component}
|
{stepsConfig[activeTab].component}
|
||||||
|
|
||||||
<div className="d-flex justify-content-between mt-4">
|
<div className="d-flex justify-content-between mt-4">
|
||||||
@ -124,21 +196,27 @@ const ManagePurchase = ({ onClose }) => {
|
|||||||
>
|
>
|
||||||
Previous
|
Previous
|
||||||
</button>
|
</button>
|
||||||
<di>
|
|
||||||
|
<div>
|
||||||
{activeTab < stepsConfig.length - 1 ? (
|
{activeTab < stepsConfig.length - 1 ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
|
disabled={isPending || isUpdating}
|
||||||
>
|
>
|
||||||
Next <i className="bx bx-sm bx-right-arrow"></i>
|
Next <i className="bx bx-sm bx-right-arrow"></i>
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button type="submit" className="btn btn-sm btn-primary">
|
<button
|
||||||
{isPending ? "Please Wait" : "SUbmit"}
|
type="submit"
|
||||||
|
className="btn btn-sm btn-primary"
|
||||||
|
disabled={isPending || isUpdating}
|
||||||
|
>
|
||||||
|
{isPending || isUpdating ? "Please Wait" : "Submit"}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</di>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</AppFormProvider>
|
</AppFormProvider>
|
||||||
@ -148,3 +226,4 @@ const ManagePurchase = ({ onClose }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default ManagePurchase;
|
export default ManagePurchase;
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { useDebounce } from "../../utils/appUtils";
|
|||||||
import { usePurchaseContext } from "../../pages/purchase/PurchasePage";
|
import { usePurchaseContext } from "../../pages/purchase/PurchasePage";
|
||||||
|
|
||||||
const PurchaseList = ({ searchString }) => {
|
const PurchaseList = ({ searchString }) => {
|
||||||
const { setViewPurchase } = usePurchaseContext();
|
const { setViewPurchase, setManagePurchase } = usePurchaseContext();
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const debounceSearch = useDebounce(searchString, 300);
|
const debounceSearch = useDebounce(searchString, 300);
|
||||||
const { data, isLoading } = usePurchasesList(
|
const { data, isLoading } = usePurchasesList(
|
||||||
@ -101,14 +101,22 @@ const PurchaseList = ({ searchString }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-eye me-2"></i>
|
<i className="bx bx-show me-2"></i>
|
||||||
<span className="align-left">view</span>
|
<span className="align-left">view</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a className="dropdown-item cursor-pointer">
|
<a
|
||||||
<i className="bx bx-trash me-2"></i>
|
className="dropdown-item cursor-pointer"
|
||||||
<span className="align-left">Add</span>
|
onClick={() =>
|
||||||
|
setManagePurchase({
|
||||||
|
isOpen: true,
|
||||||
|
purchaseId: item.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="bx bx-edit me-2"></i>
|
||||||
|
<span className="align-left">Edit</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@ -37,7 +37,7 @@ const PurchasePaymentDetails = () => {
|
|||||||
id="baseAmount"
|
id="baseAmount"
|
||||||
type="number"
|
type="number"
|
||||||
className="form-control form-control-md"
|
className="form-control form-control-md"
|
||||||
{...register("baseAmount")}
|
{...register("baseAmount", { valueAsNumber: true })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{errors?.baseAmount && (
|
{errors?.baseAmount && (
|
||||||
@ -56,7 +56,7 @@ const PurchasePaymentDetails = () => {
|
|||||||
id="taxAmount"
|
id="taxAmount"
|
||||||
type="number"
|
type="number"
|
||||||
className="form-control form-control-md"
|
className="form-control form-control-md"
|
||||||
{...register("taxAmount")}
|
{...register("taxAmount",{ valueAsNumber: true })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{errors?.taxAmount && (
|
{errors?.taxAmount && (
|
||||||
@ -75,7 +75,7 @@ const PurchasePaymentDetails = () => {
|
|||||||
id="totalAmount"
|
id="totalAmount"
|
||||||
type="number"
|
type="number"
|
||||||
className="form-control form-control-md"
|
className="form-control form-control-md"
|
||||||
{...register("totalAmount")}
|
{...register("totalAmount",{ valueAsNumber: true })}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -49,7 +49,7 @@ export const PurchaseSchema = z.object({
|
|||||||
|
|
||||||
proformaInvoiceNumber: z.string().nullable(),
|
proformaInvoiceNumber: z.string().nullable(),
|
||||||
proformaInvoiceDate: z.coerce.date().nullable(),
|
proformaInvoiceDate: z.coerce.date().nullable(),
|
||||||
proformaInvoiceAmount: z.string().nullable(),
|
proformaInvoiceAmount: z.number().optional(),
|
||||||
|
|
||||||
invoiceNumber: z.string().nullable(),
|
invoiceNumber: z.string().nullable(),
|
||||||
invoiceDate: z.coerce.date().nullable(),
|
invoiceDate: z.coerce.date().nullable(),
|
||||||
@ -59,11 +59,11 @@ export const PurchaseSchema = z.object({
|
|||||||
acknowledgmentDate: z.coerce.date().nullable(),
|
acknowledgmentDate: z.coerce.date().nullable(),
|
||||||
acknowledgmentNumber: z.string().nullable(),
|
acknowledgmentNumber: z.string().nullable(),
|
||||||
|
|
||||||
baseAmount: z.string().min(1, { message: "Base amount is required" }),
|
baseAmount: z.number().min(1, { message: "Base amount is required" }),
|
||||||
taxAmount: z.string().min(1, { message: "Tax amount is required" }),
|
taxAmount: z.number().min(1, { message: "Tax amount is required" }),
|
||||||
totalAmount: z.string().min(1, { message: "Total amount is required" }),
|
totalAmount: z.number().min(1, { message: "Total amount is required" }),
|
||||||
paymentDueDate: z.coerce.date().nullable(),
|
paymentDueDate: z.coerce.date().nullable(),
|
||||||
transportCharges: z.string().nullable(),
|
transportCharges: z.number().nullable(),
|
||||||
description: z.string().min(1, { message: "Description is required" }),
|
description: z.string().min(1, { message: "Description is required" }),
|
||||||
|
|
||||||
attachments: z.array(AttachmentSchema([], 25)).optional(),
|
attachments: z.array(AttachmentSchema([], 25)).optional(),
|
||||||
@ -98,9 +98,9 @@ export const defaultPurchaseValue = {
|
|||||||
acknowledgmentNumber: null,
|
acknowledgmentNumber: null,
|
||||||
acknowledgmentDate: null,
|
acknowledgmentDate: null,
|
||||||
|
|
||||||
baseAmount: "",
|
baseAmount: 0,
|
||||||
taxAmount: "",
|
taxAmount: 0,
|
||||||
totalAmount: "",
|
totalAmount: 0,
|
||||||
paymentDueDate: null,
|
paymentDueDate: null,
|
||||||
transportCharges: null,
|
transportCharges: null,
|
||||||
description: "",
|
description: "",
|
||||||
|
|||||||
@ -39,13 +39,13 @@ export const useOrganizationModal = () => {
|
|||||||
|
|
||||||
// ================================Query=============================================================
|
// ================================Query=============================================================
|
||||||
|
|
||||||
export const useGlobaleOrganizations = (pageSize, pageNumber, searchString) => {
|
export const useGlobaleOrganizations = (pageSize, pageNumber,id, searchString) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["global_organization",pageSize, pageNumber, searchString],
|
queryKey: ["global_organization",pageSize, pageNumber,id, searchString],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const resp = await OrganizationRepository.getGlobalOrganization(
|
const resp = await OrganizationRepository.getGlobalOrganization(
|
||||||
pageSize,
|
pageSize,
|
||||||
pageNumber,
|
pageNumber,id,
|
||||||
searchString
|
searchString
|
||||||
);
|
);
|
||||||
return resp.data;
|
return resp.data;
|
||||||
|
|||||||
@ -62,3 +62,23 @@ export const useCreatePurchaseInvoice = (onSuccessCallback) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useUpdatePurchaseInvoice = (onSuccessCallback) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({purchaseId,payload}) => PurchaseRepository.UpdatePurchase(purchaseId, payload),
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
showToast("Purchase Invoice Updated successfully", "success");
|
||||||
|
if (onSuccessCallback) onSuccessCallback();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showToast(
|
||||||
|
error?.response?.data?.message ||
|
||||||
|
error.message ||
|
||||||
|
"Failed to create invoice",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@ -18,7 +18,10 @@ export const usePurchaseContext = () => {
|
|||||||
};
|
};
|
||||||
const PurchasePage = () => {
|
const PurchasePage = () => {
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [addePurchase, setAddedPurchase] = useState(false);
|
const [managePurchase, setManagePurchase] = useState({
|
||||||
|
isOpen: false,
|
||||||
|
purchaseId: null,
|
||||||
|
});
|
||||||
const [viewPurchaseState, setViewPurchase] = useState({
|
const [viewPurchaseState, setViewPurchase] = useState({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
purchaseId: null,
|
purchaseId: null,
|
||||||
@ -26,8 +29,8 @@ const PurchasePage = () => {
|
|||||||
|
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
setViewPurchase,
|
setViewPurchase,
|
||||||
|
setManagePurchase,
|
||||||
};
|
};
|
||||||
console.log(ViewPurchase);
|
|
||||||
return (
|
return (
|
||||||
<PurchaseContext.Provider value={contextValue}>
|
<PurchaseContext.Provider value={contextValue}>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
@ -56,7 +59,12 @@ const PurchasePage = () => {
|
|||||||
<di className="col-6 text-end">
|
<di className="col-6 text-end">
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
onClick={() => setAddedPurchase(true)}
|
onClick={() =>
|
||||||
|
setManagePurchase({
|
||||||
|
isOpen: true,
|
||||||
|
purchaseId: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>Add
|
<i className="bx bx-plus-circle me-2"></i>Add
|
||||||
</button>
|
</button>
|
||||||
@ -65,20 +73,33 @@ const PurchasePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PurchaseList searchString={searchText} />
|
<PurchaseList searchString={searchText} />
|
||||||
{addePurchase && (
|
{managePurchase.isOpen && (
|
||||||
<GlobalModel
|
<GlobalModel
|
||||||
isOpen={addePurchase}
|
isOpen={managePurchase.isOpen}
|
||||||
size="lg"
|
size="lg"
|
||||||
closeModal={() => setAddedPurchase(false)}
|
closeModal={() =>
|
||||||
|
setManagePurchase({
|
||||||
|
isOpen: false,
|
||||||
|
purchaseId: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ManagePurchase onClose={() => setAddedPurchase(false)} />
|
<ManagePurchase
|
||||||
|
onClose={() =>
|
||||||
|
setManagePurchase({
|
||||||
|
isOpen: false,
|
||||||
|
purchaseId: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
purchaseId={managePurchase.purchaseId}
|
||||||
|
/>
|
||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{viewPurchaseState.isOpen && (
|
{viewPurchaseState.isOpen && (
|
||||||
<GlobalModel
|
<GlobalModel
|
||||||
isOpen={viewPurchaseState.isOpen}
|
isOpen={viewPurchaseState.isOpen}
|
||||||
size="lg"
|
size="xl"
|
||||||
closeModal={() =>
|
closeModal={() =>
|
||||||
setViewPurchase({ isOpen: false, purchaseId: null })
|
setViewPurchase({ isOpen: false, purchaseId: null })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,9 +41,11 @@ const OrganizationRepository = {
|
|||||||
return api.get(url);
|
return api.get(url);
|
||||||
},
|
},
|
||||||
|
|
||||||
getGlobalOrganization: (pageSize, pageNumber, searchString) =>
|
getGlobalOrganization: (pageSize, pageNumber, searchString, id) =>
|
||||||
api.get(
|
api.get(
|
||||||
`/api/Organization/list/basic?pageSize=${pageSize}&pageNumber=${pageNumber}&searchString=${searchString}`
|
`/api/Organization/list/basic?pageSize=${pageSize}&pageNumber=${pageNumber}${
|
||||||
|
id ? `&id=${id}` : ""
|
||||||
|
}&searchString=${searchString}`
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,10 @@ export const PurchaseRepository = {
|
|||||||
GetPurchase: (id) => api.get(`/api/PurchaseInvoice/details/${id}`),
|
GetPurchase: (id) => api.get(`/api/PurchaseInvoice/details/${id}`),
|
||||||
UpdatePurchase: (id, data) =>
|
UpdatePurchase: (id, data) =>
|
||||||
api.patch(`/api/PurchaseInvoice/edit/${id}`, data),
|
api.patch(`/api/PurchaseInvoice/edit/${id}`, data),
|
||||||
|
GetDeliveryChallan: (purchaseInvoiceId) =>
|
||||||
|
api.get(`/api/PurchaseInvoice/delivery-challan/list/${purchaseInvoiceId}`),
|
||||||
|
addDelievryChallan: (data) =>
|
||||||
|
api.post(`/api/PurchaseInvoice/delivery-challan/create`, data),
|
||||||
};
|
};
|
||||||
|
|
||||||
// const filterPayload = JSON.stringify({
|
// const filterPayload = JSON.stringify({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user