Files
Rasadyar_FrontEnd/src/features/guild/components/StewardAllocationToGuild.js
2026-01-18 16:03:27 +03:30

884 lines
28 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useContext, useEffect, useState, useCallback } from "react";
import {
Autocomplete,
Button,
Checkbox,
FormControl,
FormControlLabel,
InputAdornment,
Radio,
RadioGroup,
TextField,
Typography,
} from "@mui/material";
import { useDispatch } from "react-redux";
import { useFormik } from "formik";
import { DatePicker } from "@mui/x-date-pickers";
import moment from "moment";
import { AppContext } from "../../../contexts/AppContext";
import { provincePolicyGetUploadImageService } from "../../province/services/province-policy-upload-image";
import { getRoleFromUrl } from "../../../utils/getRoleFromUrl";
import { slaughterGetProductsService } from "../../slaughter-house/services/slaughter-inventory-gets";
import { slaughterGetGuildsForAllocateService } from "../../slaughter-house/services/slaughter-get-guilds-for-allocate";
import { Yup } from "../../../lib/yup/yup";
import { fixBase64 } from "../../../utils/toBase64";
import { CLOSE_MODAL, DRAWER } from "../../../lib/redux/slices/appSlice";
import { NumberInput } from "../../../components/number-format-custom/NumberFormatCustom";
import { ImageUpload } from "../../../components/image-upload/ImageUpload";
import { Grid } from "../../../components/grid/Grid";
import {
slaughterAllocateStewardService,
slaughterEditAllocateStewardService,
} from "../../slaughter-house/services/slaughter-allocate-steward";
import { fetchSlaughterBroadcastAndProducts } from "../../slaughter-house/services/handle-fetch-slaughter-products";
import MonthlyDataCalendar from "../../../components/date-picker/MonthlyDataCalendar";
import PersianDate from "persian-date";
import axios from "axios";
import { LabelField } from "../../../components/label-field/LabelField";
import { SPACING } from "../../../data/spacing";
export const StewardAllocationToGuild = ({
item,
key,
sellerType,
fetchData,
buyerType,
allocationType,
sellType,
updateTable,
fetchApiData,
editData,
coldHouseKey,
coldHouseItemKey,
killHouseAllocation,
priceInfo,
}) => {
const dispatch = useDispatch();
const [productData, setProductData] = useState([]);
const [guildsData, setGuildsData] = useState([]);
const [selectedInventory, setSelectedInventory] = useState("governmental");
const [approvedStatus, setApprovedStatus] = useState("true");
const [productKey, setProductKey] = useState(null);
const [openNotif] = useContext(AppContext);
const [profileImages, setProfileImages] = useState(
editData?.image ? [{ data_url: editData.image }] : []
);
const [value, setValue] = useState("own");
const [imageUploadLimit, setImageUploadLimit] = useState(1);
const [imageChanged, setImageChanged] = useState(false);
const [changeMobile, setChangeMobile] = useState(false);
const [selectedCalendarDate, setSelectedCalendarDate] = useState(null);
const [calendarDayData, setCalendarDayData] = useState({});
const [productionDate, setProductionDate] = useState(null);
const [selectedDateAmount, setSelectedDateAmount] = useState(null);
const [calendarRawData, setCalendarRawData] = useState({
governmental: [],
free: [],
});
const [selectedDate1, setSelectedDate1] = useState(
moment(new Date()).format("YYYY-MM-DD")
);
const handleChange = (event) => {
setValue(event.target.value);
setBuyerData({
key: "",
item: "",
buyerType: "",
allocationType: "",
});
};
useEffect(() => {
if (priceInfo?.active === false) {
setApprovedStatus("false");
}
}, [priceInfo?.active]);
useEffect(() => {
if (approvedStatus === "true" && priceInfo?.active) {
formik.setFieldValue("price", priceInfo?.killHousePrice);
}
}, [approvedStatus]);
const handleSellType = (event) => {
const newType = event.target.value;
setSelectedInventory(newType);
};
const handleApprovedPrice = (event) => {
const newType = event.target.value;
setApprovedStatus(newType);
};
const handleDateSelect = (dateInfo) => {
if (dateInfo && dateInfo.formattedDate) {
setSelectedCalendarDate(dateInfo.formattedDate);
const data = calendarDayData[dateInfo.formattedDate];
if (data && data.originalDay) {
setProductionDate(data.originalDay);
}
if (data && (data.amount !== undefined || data.value1 !== undefined)) {
const rawAmount = data.amount !== undefined ? data.amount : data.value1;
const normalizedAmount =
typeof rawAmount === "string"
? Number(rawAmount.replace(/,/g, ""))
: Number(rawAmount);
setSelectedDateAmount(
Number.isFinite(normalizedAmount) ? normalizedAmount : null
);
} else {
setSelectedDateAmount(null);
}
}
};
const transformCalendarData = useCallback((dataArray) => {
if (!Array.isArray(dataArray)) return {};
const transformedData = {};
dataArray.forEach((item) => {
if (item.day && item.amount !== undefined) {
const persianDate = new PersianDate(new Date(item.day));
const persianDateStr = persianDate.format("YYYY/MM/DD");
const rawAmount = item.amount;
const normalizedAmount =
typeof rawAmount === "string"
? Number(rawAmount.replace(/,/g, ""))
: Number(rawAmount);
transformedData[persianDateStr] = {
value1: normalizedAmount,
originalDay: item.day,
active: item.active === true, // Store active status
};
}
});
return transformedData;
}, []);
const updateCalendarData = useCallback(
(dataArray) => {
const transformed = transformCalendarData(dataArray);
setCalendarDayData(transformed);
},
[transformCalendarData]
);
const fetchCalendarData = useCallback(
async (dateParam = selectedDate1) => {
try {
const response = await axios.get("/steward-remain-weight/", {
params: {
date: dateParam,
},
});
if (response.data) {
setCalendarRawData({
governmental: response.data.governmental || [],
free: response.data.free || [],
});
const dataToShow =
selectedInventory === "governmental"
? response.data.governmental
: response.data.free;
updateCalendarData(dataToShow || []);
}
} catch (error) {
console.error("Error fetching calendar data:", error);
}
},
[selectedInventory, updateCalendarData, selectedDate1]
);
const [buyerData, setBuyerData] = useState({
key,
item,
buyerType,
allocationType,
});
useEffect(() => {
if (getRoleFromUrl() === "Steward") {
setValue("free");
}
}, []);
useEffect(() => {
fetchCalendarData(selectedDate1);
}, [fetchCalendarData, selectedDate1]);
useEffect(() => {
if (
calendarRawData.governmental.length > 0 ||
calendarRawData.free.length > 0
) {
const dataToShow =
selectedInventory === "governmental"
? calendarRawData.governmental
: calendarRawData.free;
updateCalendarData(dataToShow);
setSelectedCalendarDate(null);
setProductionDate(null);
setSelectedDateAmount(null);
}
}, [selectedInventory, calendarRawData, updateCalendarData]);
useEffect(() => {
dispatch(provincePolicyGetUploadImageService()).then((r) => {
if (r.payload?.data) {
setImageUploadLimit(r.payload.data.killHouseAllocation);
}
});
if (!editData) {
dispatch(slaughterGetProductsService()).then((r) => {
setProductData(r.payload.data);
});
if (!item) {
dispatch(
slaughterGetGuildsForAllocateService({
free: value === "free" ? true : false,
})
).then((r) => {
setGuildsData(r.payload.data);
});
}
}
}, [dispatch, value]);
const validationSchema = Yup.object({
mobile: Yup.string().when([], {
is: () => !editData,
then: (schema) =>
schema
.required("شماره موبایل الزامی است")
.min(11, "شماره موبایل باید 11 رقم باشد")
.max(11, "شماره موبایل باید 11 رقم باشد")
.matches(
/^09\d{9}$/,
"شماره موبایل باید با 09 شروع شود و 11 رقم باشد"
),
otherwise: (schema) => schema.notRequired(),
}),
weight: Yup.number()
.required("این فیلد اجباری است!")
.integer("عدد باید صحیح باشد!")
.min(1, "یک مقدار مثبت وارد کنید!")
.test(
"max-production-date-amount",
`وزن نمی‌تواند بیشتر از موجودی تاریخ تولید (${
selectedDateAmount?.toLocaleString() || 0
} کیلوگرم) باشد!`,
function (value) {
if (!selectedDateAmount || selectedDateAmount === null) return true;
return (
value <= selectedDateAmount + (editData?.realWeightOfCarcasses || 0)
);
}
),
price: Yup.number()
.required("این فیلد اجباری است!")
.min(1, "یک مقدار مثبت وارد کنید!"),
wholePrice: Yup.number()
.required("این فیلد اجباری است!")
.min(1, "یک مقدار مثبت وارد کنید!"),
...(killHouseAllocation && {
image: Yup.string().when([], {
is: () => (!editData || imageChanged) && imageUploadLimit > 0,
then: Yup.string().required("عکس الزامی است"),
otherwise: Yup.string().notRequired(),
}),
}),
});
const factorPaymentHandler = (imageList) => {
if (imageList[0]) {
formik.setFieldValue("image", fixBase64(imageList[0]?.data_url));
setImageChanged(true);
} else {
formik.setFieldValue("image", "");
setImageChanged(true);
}
setProfileImages(imageList);
};
const formik = useFormik({
initialValues: {
mobile: "",
weight: editData?.realWeightOfCarcasses || "",
wholePrice: editData?.totalAmount || "",
price: editData?.amount || "",
image: editData?.image || "",
},
validationSchema,
});
useEffect(() => {
formik.validateForm();
}, []);
useEffect(() => {
formik.validateForm();
}, [selectedDateAmount]);
useEffect(() => {
if (formik.values.weight && formik.values.price) {
formik.setFieldValue(
"wholePrice",
formik.values.price * formik.values.weight
);
}
}, [formik.values.price, formik.values.weight]);
const successSubmit = () => {
dispatch(CLOSE_MODAL());
openNotif({
vertical: "top",
horizontal: "center",
msg: "عملیات با موفقیت انجام شد.",
severity: "success",
});
dispatch(fetchSlaughterBroadcastAndProducts());
dispatch(
DRAWER({
right: false,
bottom: false,
left: false,
content: null,
})
);
fetchApiData && fetchApiData(1);
updateTable && updateTable();
fetchData && fetchData(1);
};
const [dateRangeError, setDateRangeError] = useState(null);
return (
<Grid
container
xs={12}
direction="column"
justifyContent="center"
alignItems="flex-start"
gap={1.8}
>
{!editData && (
<DatePicker
label="تاریخ ثبت توزیع"
id="date"
renderInput={(params) => (
<TextField
fullWidth
{...params}
error={Boolean(dateRangeError) || params.error}
helperText={dateRangeError || params.helperText}
/>
)}
shouldDisableDate={(date) => {
const d = moment(date);
const today = moment();
const yesterday = moment().subtract(1, "day");
return !(d.isSame(today, "day") || d.isSame(yesterday, "day"));
}}
value={selectedDate1}
onChange={(e) => {
if (!e) {
setDateRangeError(null);
return;
}
const d = moment(e);
const today = moment();
const yesterday = moment().subtract(1, "day");
const isAllowed =
d.isSame(today, "day") || d.isSame(yesterday, "day");
if (!isAllowed) {
setDateRangeError(
"تنها امکان انتخاب «امروز» یا «دیروز» وجود دارد."
);
return;
}
setDateRangeError(null);
const formatted = moment(e).format("YYYY-MM-DD");
setSelectedDate1(formatted);
fetchCalendarData(formatted);
}}
/>
)}
{!editData && !coldHouseKey && (
<Grid xs={12} container>
<Autocomplete
fullWidth
style={{ minWidth: 210 }}
disablePortal
id="hatching"
options={
productData
? productData.map((i) => {
return {
data: i,
label: `${i.name}`,
};
})
: []
}
onChange={(event, value) => {
setProductKey(value.data);
}}
renderInput={(params) => (
<TextField fullWidth {...params} label="انتخاب محصول" />
)}
/>
</Grid>
)}
{!editData && (
<LabelField label="خریداران">
<FormControl fullWidth>
<RadioGroup
row
aria-labelledby="demo-controlled-radio-buttons-group"
name="controlled-radio-buttons-group"
value={value}
onChange={handleChange}
sx={{
justifyContent: "space-between",
}}
>
<FormControlLabel
value="own"
control={<Radio />}
label="صنوف اختصاصی"
/>
<FormControlLabel
value="free"
control={<Radio />}
label="صنوف آزاد"
/>
</RadioGroup>
</FormControl>
</LabelField>
)}
{!item && !editData && (
<Grid xs={12} container>
<Autocomplete
fullWidth
style={{ minWidth: 210 }}
disablePortal
id="hatching"
options={
guildsData
? guildsData.map((i) => {
return {
data: i,
label: `${i?.steward ? "مباشر" : "صنف"} ${
i?.guildsName
} ${i?.user?.fullname} (${i?.user?.mobile})`,
};
})
: []
}
onChange={(event, value) => {
setBuyerData({
item: value?.data,
key: value?.data?.key,
allocationType: value?.data?.steward
? "steward_steward"
: "steward_guild",
buyerType: value?.data?.steward ? "Steward" : "Guild",
});
formik.setFieldValue("mobile", value?.data?.user?.mobile);
formik.setFieldTouched("mobile", true, false);
formik.validateField("mobile");
const reg = new RegExp(/^09\d{9}$/);
if (!reg.test(value?.data?.user?.mobile)) {
setChangeMobile(true);
}
}}
renderInput={(params) => (
<TextField fullWidth {...params} label="انتخاب مباشر / صنف" />
)}
/>
</Grid>
)}
{!item && !editData && (
<Grid
container
xs={12}
alignItems="center"
justifyContent="center"
p={1}
gap={SPACING.TINY}
sx={{
border: 2,
borderColor: "#e6e6e6",
borderRadius: 2,
}}
>
<Typography variant="caption" color="error">
<Checkbox
sx={{ ml: -1.25 }}
checked={changeMobile}
onChange={() => setChangeMobile(!changeMobile)}
/>
از این قسمت میتوانید تلفن مباشر/صنف را ویرایش کنید.
</Typography>
{buyerData?.key && changeMobile && (
<TextField
fullWidth
id="mobile"
value={formik.values.mobile}
error={
formik.touched.mobile ? Boolean(formik.errors.mobile) : null
}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
helperText={
formik.touched.mobile && Boolean(formik.errors.mobile)
? formik.errors.mobile
: null
}
label="موبایل"
autoComplete="current-password"
variant="outlined"
/>
)}
</Grid>
)}
{!item && !editData && priceInfo?.active !== false && (
<LabelField label="نوع فروش">
<FormControl fullWidth>
<RadioGroup
row
aria-labelledby="segment-type-radio-group"
name="segmentType"
value={approvedStatus}
onChange={handleApprovedPrice}
sx={{
justifyContent: "space-between",
}}
>
<FormControlLabel
value={true}
control={<Radio />}
label="قیمت دولتی"
/>
<FormControlLabel
value={false}
control={<Radio />}
label="قیمت آزاد"
/>
</RadioGroup>
</FormControl>
</LabelField>
)}
{!item && !editData && (
<LabelField label="نوع انبار">
<FormControl fullWidth>
<RadioGroup
row
aria-labelledby="segment-type-radio-group"
name="segmentType"
value={selectedInventory}
onChange={handleSellType}
sx={{
justifyContent: "space-between",
}}
>
<FormControlLabel
value="governmental"
control={<Radio />}
label="دولتی"
/>
<FormControlLabel value="free" control={<Radio />} label="آزاد" />
</RadioGroup>
</FormControl>
</LabelField>
)}
<Grid
container
xs={12}
justifyContent="center"
alignItems="center"
gap={SPACING.TINY}
sx={{ width: "100%" }}
direction="column"
>
<MonthlyDataCalendar
onDateSelect={handleDateSelect}
dayData={calendarDayData}
selectedDate={selectedCalendarDate}
maxGregorianDate={selectedDate1}
label={`تاریخ تولید گوشت ${
selectedDateAmount !== null
? `(موجودی: ${selectedDateAmount?.toLocaleString()} کیلوگرم)`
: ""
}`}
/>
{productionDate &&
selectedDate1 &&
moment(productionDate).isAfter(moment(selectedDate1), "day") && (
<Typography
sx={{
color: "#d32f2f",
fontSize: "0.75rem",
marginTop: "4px",
marginRight: "14px",
textAlign: "right",
}}
>
تاریخ تولید نمیتواند بعد از تاریخ انتخابی باشد
</Typography>
)}
</Grid>
<NumberInput
allowLeadingZeros
thousandSeparator=","
decimalScale={0}
allowNegative={false}
fullWidth
id="weight"
label="وزن لاشه"
variant="outlined"
value={formik.values.weight}
error={
!selectedDateAmount && !productionDate
? true
: formik.touched.weight
? Boolean(formik.errors.weight)
: selectedDateAmount && formik.values.weight > selectedDateAmount
}
onChange={(e) => {
const value = e.target.value;
if (value === "" || value === null || value === undefined) {
formik.setFieldValue("weight", "");
return;
}
const intValue = Math.floor(Number(value));
if (intValue > 0) {
formik.setFieldValue("weight", intValue);
} else if (intValue === 0) {
formik.setFieldValue("weight", "");
}
}}
onBlur={formik.handleBlur}
helperText={
!selectedDateAmount && !productionDate
? "لطفاً ابتدا تاریخ تولید را انتخاب کنید!"
: formik.touched.weight && Boolean(formik.errors.weight)
? formik.errors.weight
: null
}
disabled={!selectedDateAmount && !productionDate}
sx={{
"& .MuiFormHelperText-root": {
color:
selectedDateAmount && formik.values.weight > selectedDateAmount
? "error.main"
: undefined,
},
}}
/>
<NumberInput
allowLeadingZeros
thousandSeparator=","
fullWidth
id="price"
label="قیمت هر کیلوگرم"
variant="outlined"
InputProps={{
endAdornment: <InputAdornment position="start">ریال</InputAdornment>,
}}
value={formik.values.price}
error={formik.touched.price ? Boolean(formik.errors.price) : null}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
helperText={
formik.touched.price && Boolean(formik.errors.price)
? formik.errors.price
: null
}
/>
<NumberInput
disabled
allowLeadingZeros
thousandSeparator=","
fullWidth
id="wholePrice"
label="هزینه کل"
variant="outlined"
InputProps={{
endAdornment: <InputAdornment position="start">ریال</InputAdornment>,
}}
value={formik.values.wholePrice}
error={
formik.touched.wholePrice ? Boolean(formik.errors.wholePrice) : null
}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
helperText={
formik.touched.wholePrice && Boolean(formik.errors.wholePrice)
? formik.errors.wholePrice
: null
}
/>
{(killHouseAllocation || (editData && editData.image)) && (
<Grid container xs={12} justifyContent="center" alignItems="center">
<ImageUpload
onChange={factorPaymentHandler}
images={profileImages}
maxNumber={1}
title={"بارگزاری سند"}
/>
{formik.touched.image && Boolean(formik.errors.image) && (
<Typography color="error">ثبت تصویر الزامی است</Typography>
)}
</Grid>
)}
<Grid container xs={12} spacing={SPACING.SMALL}>
<Grid xs={6}>
<Button
variant="contained"
fullWidth
disabled={
editData
? !formik.isValid
: !formik.isValid ||
(coldHouseKey ? false : !productKey) ||
!buyerData?.item?.key ||
!productionDate ||
(productionDate &&
selectedDate1 &&
moment(selectedDate1).isBefore(
moment(productionDate),
"day"
))
}
onClick={() => {
let req = {};
if (coldHouseItemKey) {
req = {
allocation_key: coldHouseItemKey,
number_of_carcasses: 0,
weight_of_carcasses: formik.values.weight,
amount: formik.values.price,
total_amount: formik.values.wholePrice,
distribution_type: "web",
...(imageChanged && { image: formik.values.image }),
};
} else if (!editData) {
req = {
seller_type: sellerType,
buyer_type: buyerData?.buyerType,
guild_key:
buyerData?.buyerType === "Guild"
? buyerData?.item?.key
: null,
steward_key:
buyerData?.buyerType === "Steward"
? buyerData?.item?.key
: null,
kill_house_key:
buyerData?.buyerType === "KillHouse"
? buyerData?.item?.key
: null,
cold_house_key: coldHouseKey || null,
product_key: coldHouseKey ? null : productKey.key,
type: "manual",
allocation_type: coldHouseKey
? "ColdHouse"
: buyerData?.allocationType,
number_of_carcasses: 0,
weight_of_carcasses: formik.values.weight,
sell_type: sellType,
amount: formik.values.price,
total_amount: formik.values.wholePrice,
approved_price_status:
approvedStatus === "true" ? true : false,
quota: selectedInventory,
date: selectedDate1,
production_date: productionDate,
distribution_type: "web",
...(buyerData?.item?.user?.mobile !== formik.values.mobile
? { interface_number: formik.values.mobile }
: {}),
...(profileImages.length > 0 && {
image: formik.values.image,
}),
};
req = Object.fromEntries(
Object.entries(req).filter(([, value]) => value !== null)
);
} else {
req = {
allocation_key: editData?.key,
number_of_carcasses: 0,
weight_of_carcasses: formik.values.weight,
amount: formik.values.price,
total_amount: formik.values.wholePrice,
distribution_type: "web",
...(imageChanged && { image: formik.values.image }),
};
}
if (!editData) {
dispatch(slaughterAllocateStewardService(req)).then((r) => {
if (r.payload.error) {
openNotif({
vertical: "top",
horizontal: "center",
msg: r.payload.error,
severity: "error",
});
} else {
successSubmit();
}
});
} else {
dispatch(slaughterEditAllocateStewardService(req)).then((r) => {
if (r.payload.error) {
openNotif({
vertical: "top",
horizontal: "center",
msg: r.payload.error,
severity: "error",
});
} else {
successSubmit();
}
});
}
}}
>
{editData ? "ویرایش" : "ثبت"}
</Button>
</Grid>
<Grid xs={6}>
<Button
fullWidth
variant="outlined"
color="primary"
onClick={() => {
dispatch(DRAWER({ right: false, bottom: false, content: null }));
}}
>
انصراف
</Button>
</Grid>
</Grid>
</Grid>
);
};