1528 lines
60 KiB
React
1528 lines
60 KiB
React
|
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|||
|
|
import { useNavigate, useLocation } from 'react-router-dom';
|
|||
|
|
import { IoBagRemove } from 'react-icons/io5';
|
|||
|
|
import { useDispatch } from 'react-redux';
|
|||
|
|
import {
|
|||
|
|
ArrowRightOutlined,
|
|||
|
|
PlusCircleOutlined,
|
|||
|
|
EditFilled,
|
|||
|
|
} from '@ant-design/icons';
|
|||
|
|
import { Form, Tooltip, Table, Input, Collapse, Select } from 'antd';
|
|||
|
|
import { IoAddCircleSharp } from 'react-icons/io5';
|
|||
|
|
import { InputField } from '../../Components/Forms/InputField.jsx';
|
|||
|
|
import Buttons from '../../Components/Forms/Buttons.jsx';
|
|||
|
|
import { Messages } from '../../Components/Notifications/Messages.jsx';
|
|||
|
|
import { DeleteFilled } from '@ant-design/icons';
|
|||
|
|
import { changeBreadCrumb } from '../../Features/AppPage/CenterPage.js';
|
|||
|
|
import FormHeader from '../PageComponents/FormHeader.jsx';
|
|||
|
|
import { DefaultModal } from '../../Components/Modal/DefaultModal.jsx';
|
|||
|
|
import { getSession } from '../../Services/Others.js';
|
|||
|
|
import { DatePicProd } from '../../Components/Forms/DatePickerProduct.jsx';
|
|||
|
|
import moment from 'moment';
|
|||
|
|
import './openingStockForm.scss';
|
|||
|
|
import {
|
|||
|
|
getProductStockDetails,
|
|||
|
|
postOpeningStock,
|
|||
|
|
} from '../../Features/PurchaseOrder/PurchaseOrder.js';
|
|||
|
|
|
|||
|
|
const subDirectory = import.meta.env.BASE_URL;
|
|||
|
|
const openingStockForm = () => {
|
|||
|
|
const { Panel } = Collapse;
|
|||
|
|
const formRef = useRef(null);
|
|||
|
|
const productAddInfoRef = useRef(null);
|
|||
|
|
|
|||
|
|
const dispatch = useDispatch();
|
|||
|
|
const navigate = useNavigate();
|
|||
|
|
const location = useLocation();
|
|||
|
|
const [form] = Form.useForm();
|
|||
|
|
const [editingKey, setEditingKey] = useState('');
|
|||
|
|
const [index, setindex] = useState();
|
|||
|
|
const state = location?.state;
|
|||
|
|
const editstate = state?.editstate;
|
|||
|
|
const CompId = getSession('CompId');
|
|||
|
|
const BranchId = getSession('BranchId');
|
|||
|
|
const AppId = getSession('AppId');
|
|||
|
|
const UserId = getSession('UserId');
|
|||
|
|
|
|||
|
|
const [messageType, setMessageType] = useState(null);
|
|||
|
|
const [messageData, setMessageData] = useState(null);
|
|||
|
|
|
|||
|
|
const [PurchaseData, setPurchaseData] = useState([]);
|
|||
|
|
const [additionalInfoModal, setAdditionalInfoModal] = useState(false);
|
|||
|
|
const [selectedRowRecord, setSelectedRowRecord] = useState({});
|
|||
|
|
|
|||
|
|
console.log(selectedRowRecord, 'selectedRowRecord');
|
|||
|
|
const [Delete, setDelete] = useState(false);
|
|||
|
|
const [ProductStockData, seProductStockData] = useState([]);
|
|||
|
|
const [filteredProductStockData, setFilteredProductStockData] = useState([]);
|
|||
|
|
const [eliminatedProducts, setEliminatedProducts] = useState([]);
|
|||
|
|
const [eliminateModal, setEliminateModal] = useState(false);
|
|||
|
|
const [additionalStockEntries, setAdditionalStockEntries] = useState([]);
|
|||
|
|
const [searchText, setSearchText] = useState('');
|
|||
|
|
const [activeAccordionKeys, setActiveAccordionKeys] = useState(['1']);
|
|||
|
|
const [hasInitializedEditing, setHasInitializedEditing] = useState(false);
|
|||
|
|
console.log(additionalStockEntries, 'additionalStockEntries');
|
|||
|
|
console.log(filteredProductStockData, 'filteredProductStockData');
|
|||
|
|
const items = [
|
|||
|
|
{
|
|||
|
|
name: 'Home',
|
|||
|
|
link: `${subDirectory}app-page/home`,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
name: 'Opening Stock',
|
|||
|
|
link: `${subDirectory}setting/opening-stock`,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: 'New',
|
|||
|
|
link: null,
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
useEffect(() => {
|
|||
|
|
dispatch(changeBreadCrumb({ items: items }));
|
|||
|
|
|
|||
|
|
ProductStockFun();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
const ProductStockFun = async () => {
|
|||
|
|
try {
|
|||
|
|
const Data = { AppId, CompId, BranchId, type: 'A' };
|
|||
|
|
let response = await dispatch(getProductStockDetails(Data)).unwrap();
|
|||
|
|
if (response?.data?.statusCode === 1) {
|
|||
|
|
seProductStockData(response?.data?.data);
|
|||
|
|
setFilteredProductStockData(response?.data?.data);
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('Error fetching stock details:', err);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const isEditing = (record, index) => index === editingKey;
|
|||
|
|
|
|||
|
|
const edit = (record, index) => {
|
|||
|
|
form.setFieldsValue({ ...record });
|
|||
|
|
|
|||
|
|
setEditingKey(index);
|
|||
|
|
setindex(index);
|
|||
|
|
|
|||
|
|
if (Delete) {
|
|||
|
|
form.setFieldsValue({
|
|||
|
|
TotalQty: { [index]: undefined },
|
|||
|
|
TotalPiece: { [index]: undefined },
|
|||
|
|
});
|
|||
|
|
setDelete(false);
|
|||
|
|
} else {
|
|||
|
|
form.setFieldsValue({
|
|||
|
|
TotalQty: { [index]: record?.TotalQty },
|
|||
|
|
TotalPiece: { [index]: record?.TotalPiece },
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (!hasInitializedEditing && filteredProductStockData?.length > 0) {
|
|||
|
|
edit(filteredProductStockData[0], 0);
|
|||
|
|
setHasInitializedEditing(true);
|
|||
|
|
}
|
|||
|
|
}, [filteredProductStockData, hasInitializedEditing]);
|
|||
|
|
|
|||
|
|
const save = async (index) => {
|
|||
|
|
try {
|
|||
|
|
const row = await form?.validateFields();
|
|||
|
|
|
|||
|
|
const modifiedObject = {};
|
|||
|
|
for (const key in row) {
|
|||
|
|
const newKey = key.slice(0, -1); // Remove the last character ("0") from the key
|
|||
|
|
modifiedObject[newKey] = row[key];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const newData = [...filteredProductStockData];
|
|||
|
|
if (index > -1) {
|
|||
|
|
const item = newData[index];
|
|||
|
|
newData.splice(index, 1, { ...item, ...modifiedObject });
|
|||
|
|
setFilteredProductStockData(newData);
|
|||
|
|
setEditingKey('');
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('Save failed:', err);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleKeyPress = async (e, record, index) => {
|
|||
|
|
if (e.key === 'Enter') {
|
|||
|
|
try {
|
|||
|
|
await form.validateFields();
|
|||
|
|
save(index);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Save failed:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const openAdditionalInfoModal = (record, index) => {
|
|||
|
|
console.log(record, 'recordrecord');
|
|||
|
|
|
|||
|
|
if (
|
|||
|
|
!record?.TotalQty ||
|
|||
|
|
record?.TotalQty === '' ||
|
|||
|
|
record?.TotalQty === 0
|
|||
|
|
) {
|
|||
|
|
setMessageType('error');
|
|||
|
|
setMessageData('Please enter Total Qty first');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// if (!record?.TotalPiece || record?.TotalPiece === "" || record?.TotalPiece === 0) {
|
|||
|
|
// setMessageType("error")
|
|||
|
|
// setMessageData("Please enter Total Pieces first")
|
|||
|
|
// return
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
setSelectedRowRecord(record);
|
|||
|
|
|
|||
|
|
// Load existing modal details for this product
|
|||
|
|
if (record.modalDetails && record.modalDetails.length > 0) {
|
|||
|
|
setAdditionalStockEntries(record.modalDetails);
|
|||
|
|
} else {
|
|||
|
|
setAdditionalStockEntries([]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setAdditionalInfoModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const columns = [
|
|||
|
|
{
|
|||
|
|
title: 'SI.NO',
|
|||
|
|
align: 'center',
|
|||
|
|
key: 'sno',
|
|||
|
|
render: (text, object, index) => (
|
|||
|
|
<a className="openingStock-sno-link">{index + 1}</a>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: 'Product Name',
|
|||
|
|
dataIndex: 'ProdName',
|
|||
|
|
key: 'ProdName',
|
|||
|
|
align: 'left',
|
|||
|
|
render: (text, record, index) => (
|
|||
|
|
<a className="openingStock-product-name-link">
|
|||
|
|
{record?.ProdName + '(' + record?.ProdVariantName + ')'}
|
|||
|
|
</a>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
// {
|
|||
|
|
// title: 'Uom',
|
|||
|
|
// dataIndex: 'UomName',
|
|||
|
|
// key: 'UOMName',
|
|||
|
|
// },
|
|||
|
|
{
|
|||
|
|
title: 'Total Qty',
|
|||
|
|
dataIndex: 'TotalQty',
|
|||
|
|
key: 'TotalQty',
|
|||
|
|
editable: true,
|
|||
|
|
render: (text, record, index) => {
|
|||
|
|
return isEditing(record, index) ? (
|
|||
|
|
<Form.Item
|
|||
|
|
name={'TotalQty' + index}
|
|||
|
|
rules={[
|
|||
|
|
{
|
|||
|
|
required: true,
|
|||
|
|
pattern: /^(?!0+$)(?!0+\.0*$)\d*\.?\d+$/,
|
|||
|
|
message: 'Please enter valid quantity',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
validator: (_, value) => {
|
|||
|
|
if (value && value.toString().length > 10) {
|
|||
|
|
return Promise.reject(
|
|||
|
|
'Quantity cannot exceed 10 characters'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
return Promise.resolve();
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
]}
|
|||
|
|
>
|
|||
|
|
<Input
|
|||
|
|
onPressEnter={(e) => handleKeyPress(e, record, index)}
|
|||
|
|
onBlur={(e) => handleQtyChange(e, record)}
|
|||
|
|
onChange={(e) => handleQtyChange(e, record, index)}
|
|||
|
|
inputMode="decimal"
|
|||
|
|
onInput={(e) => {
|
|||
|
|
let value = e.target.value.replace(/[^0-9.]/g, '');
|
|||
|
|
|
|||
|
|
if (value.startsWith('.')) {
|
|||
|
|
value = '0' + value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const dotCount = (value.match(/\./g) || []).length;
|
|||
|
|
if (dotCount > 1) {
|
|||
|
|
const firstDotIndex = value.indexOf('.');
|
|||
|
|
value =
|
|||
|
|
value.substring(0, firstDotIndex + 1) +
|
|||
|
|
value.substring(firstDotIndex + 1).replace(/\./g, '');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
e.target.value = value;
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
) : (
|
|||
|
|
text
|
|||
|
|
);
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: 'Enter The Pieces',
|
|||
|
|
dataIndex: 'TotalPiece',
|
|||
|
|
key: 'TotalPiece',
|
|||
|
|
editable: true,
|
|||
|
|
render: (text, record, index) => {
|
|||
|
|
return isEditing(record, index) ? (
|
|||
|
|
<Form.Item
|
|||
|
|
name={'TotalPiece' + index}
|
|||
|
|
rules={[
|
|||
|
|
{
|
|||
|
|
required: true,
|
|||
|
|
pattern: /^(?!0+$)(?!0+\.0*$)\d*\.?\d+$/,
|
|||
|
|
message: 'Please enter valid quantity',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
validator: (_, value) => {
|
|||
|
|
if (value && value.toString().length > 10) {
|
|||
|
|
return Promise.reject(
|
|||
|
|
'Quantity cannot exceed 10 characters'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
return Promise.resolve();
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
]}
|
|||
|
|
>
|
|||
|
|
<Input
|
|||
|
|
onPressEnter={(e) => handleKeyPress(e, record, index)}
|
|||
|
|
onBlur={(e) => handleTotalPieceChange(e, record)}
|
|||
|
|
onChange={(e) => handleTotalPieceChange(e, record, index)}
|
|||
|
|
inputMode="decimal"
|
|||
|
|
onInput={(e) => {
|
|||
|
|
let value = e.target.value.replace(/[^0-9.]/g, '');
|
|||
|
|
|
|||
|
|
if (value.startsWith('.')) {
|
|||
|
|
value = '0' + value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const dotCount = (value.match(/\./g) || []).length;
|
|||
|
|
if (dotCount > 1) {
|
|||
|
|
const firstDotIndex = value.indexOf('.');
|
|||
|
|
value =
|
|||
|
|
value.substring(0, firstDotIndex + 1) +
|
|||
|
|
value.substring(firstDotIndex + 1).replace(/\./g, '');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
e.target.value = value;
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
) : (
|
|||
|
|
text
|
|||
|
|
);
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// {
|
|||
|
|
// title: 'Amount',
|
|||
|
|
// dataIndex: 'Amount',
|
|||
|
|
// key: 'Amount',
|
|||
|
|
// editable: true,
|
|||
|
|
// },
|
|||
|
|
{
|
|||
|
|
title: 'Additional Info',
|
|||
|
|
dataIndex: 'AdditionalInfo',
|
|||
|
|
key: 'AdditionalInfo',
|
|||
|
|
width: 120,
|
|||
|
|
align: 'center',
|
|||
|
|
render: (text, record, index) => {
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
{' '}
|
|||
|
|
<IoAddCircleSharp
|
|||
|
|
className="openingStock-additional-info-icon shape-preview"
|
|||
|
|
onClick={() => openAdditionalInfoModal(record, index)}
|
|||
|
|
/>
|
|||
|
|
</>
|
|||
|
|
);
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
// {
|
|||
|
|
// title: "Action",
|
|||
|
|
// dataIndex: "Action",
|
|||
|
|
// key: "Action",
|
|||
|
|
// align: "center",
|
|||
|
|
// render: (_, record, index) => (
|
|||
|
|
// <a
|
|||
|
|
// onClick={(e) => {
|
|||
|
|
// e.stopPropagation();
|
|||
|
|
// }}
|
|||
|
|
// >
|
|||
|
|
// <DeleteFilled
|
|||
|
|
// style={{
|
|||
|
|
// color: "#FF4D4F",
|
|||
|
|
// }}
|
|||
|
|
// onClick={() => statusFormatters(record, index)}
|
|||
|
|
// />
|
|||
|
|
|
|||
|
|
// </a>
|
|||
|
|
// ),
|
|||
|
|
// },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const statusFormatters = (record, index) => {
|
|||
|
|
const Data = PurchaseData.filter((_, i) => i !== index);
|
|||
|
|
setDelete(true);
|
|||
|
|
form.setFieldsValue({
|
|||
|
|
[`TotalQty${index}`]: undefined,
|
|||
|
|
[`ReceivedQty${index}`]: undefined,
|
|||
|
|
// [`AcceptedQty${index}`]: undefined,
|
|||
|
|
// [`RejectedQty${index}`]: undefined,
|
|||
|
|
// [`Amount${index}`]: undefined,
|
|||
|
|
// [`FreeQty${index}`]: undefined,
|
|||
|
|
// [`InwardPrice${index}`]: undefined,
|
|||
|
|
// [`MRP${index}`]: undefined,
|
|||
|
|
// [`PurcDisc${index}`]: undefined,
|
|||
|
|
// [`SellPrice${index}`]: undefined,
|
|||
|
|
// [`WhSalePrice${index}`]: undefined,
|
|||
|
|
// [`offerSalePrice${index}`]: undefined,
|
|||
|
|
// [`splSalePrice${index}`]: undefined
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
setPurchaseData(Data);
|
|||
|
|
|
|||
|
|
formRef?.current?.setFieldsValue({ ProdId: null });
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleQtyChange = (e, record, index) => {
|
|||
|
|
const inputValue = e.target.value;
|
|||
|
|
|
|||
|
|
// if (!inputValue) return;
|
|||
|
|
|
|||
|
|
if (!/^\d*\.?\d*$/.test(inputValue)) {
|
|||
|
|
setMessageType('error');
|
|||
|
|
setMessageData('Only numeric values are allowed');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const qty = inputValue;
|
|||
|
|
|
|||
|
|
// Update the specific product by ProdId to avoid index mismatches when the
|
|||
|
|
// displayed list is filtered (search). Using index from the filtered map
|
|||
|
|
// can point to the wrong item in the underlying array.
|
|||
|
|
const newData = filteredProductStockData.map((item) => {
|
|||
|
|
if (
|
|||
|
|
item?.ProdId === record?.ProdId &&
|
|||
|
|
item?.ProdVariantName === record?.ProdVariantName
|
|||
|
|
) {
|
|||
|
|
const { modalDetails, ...rest } = item;
|
|||
|
|
return {
|
|||
|
|
...rest,
|
|||
|
|
TotalQty: qty,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
return item;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
setFilteredProductStockData(newData);
|
|||
|
|
// seProductStockData(newData);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleTotalPieceChange = (e, record, index) => {
|
|||
|
|
if (record?.OnePcsAvailable !== 'Y') {
|
|||
|
|
e.target.value = record?.TotalPiece ?? '';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const inputValue = e.target.value;
|
|||
|
|
|
|||
|
|
if (!inputValue) return;
|
|||
|
|
|
|||
|
|
if (record?.OnePcsAvailable === 'N') {
|
|||
|
|
setMessageType('error');
|
|||
|
|
setMessageData('Total Piece entry not allowed when OnePcsAvailable is N');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!/^\d*\.?\d*$/.test(inputValue)) {
|
|||
|
|
setMessageType('error');
|
|||
|
|
setMessageData('Only numeric values are allowed');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const qty = inputValue;
|
|||
|
|
|
|||
|
|
// Update by ProdId instead of using the index from the filtered view.
|
|||
|
|
const newData = filteredProductStockData.map((item) => {
|
|||
|
|
if (
|
|||
|
|
item?.ProdId === record?.ProdId &&
|
|||
|
|
item?.ProdVariantName === record?.ProdVariantName
|
|||
|
|
) {
|
|||
|
|
return {
|
|||
|
|
...item,
|
|||
|
|
TotalPiece: qty,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
return item;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
setFilteredProductStockData(newData);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const onFinish = async () => {
|
|||
|
|
let ProductDetails = filteredProductStockData
|
|||
|
|
?.filter((item) => item?.TotalQty > 0 || item?.TotalPiece > 0)
|
|||
|
|
?.map((item) => {
|
|||
|
|
return {
|
|||
|
|
ProdId: item?.ProdId,
|
|||
|
|
ProdVariantName: item?.ProdVariantName,
|
|||
|
|
OnePcsAvailable: item?.OnePcsAvailable || 'N',
|
|||
|
|
TotalQty: item?.TotalQty,
|
|||
|
|
TotalPcs: item?.TotalPiece || 0,
|
|||
|
|
CreatedBy: UserId,
|
|||
|
|
InwardDetails: item?.modalDetails
|
|||
|
|
? item?.modalDetails?.map((item1) => {
|
|||
|
|
return {
|
|||
|
|
BatchRef: item1?.BatchRef,
|
|||
|
|
AcceptedQty: item1?.Qty,
|
|||
|
|
BalanceQty: item1?.Qty,
|
|||
|
|
InwardPrice: item1?.SellPrice || 0,
|
|||
|
|
MRP: item1?.MRP || 0,
|
|||
|
|
SellPrice: item1?.SellPrice || 0,
|
|||
|
|
WhSalePrice: item1?.WhSalePrice || 0,
|
|||
|
|
OfferPrice: item1?.OfferPrice || 0,
|
|||
|
|
SpecialPrice: item1?.SpecialPrice || 0,
|
|||
|
|
InwardDate: item1?.ManufDate?.toISOString(),
|
|||
|
|
ProdVariantName: item?.ProdVariantName,
|
|||
|
|
OnePcsPrice: item1?.AmountPerPiece || 0,
|
|||
|
|
NoOfPcs: item1?.OnePieceQty || 0,
|
|||
|
|
ModelNumber: item1?.ModelNumber,
|
|||
|
|
OnePcsAvailable: item?.OnePcsAvailable || 'N',
|
|||
|
|
};
|
|||
|
|
})
|
|||
|
|
: [],
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
let PostData = {
|
|||
|
|
AppId: AppId,
|
|||
|
|
CompId: CompId,
|
|||
|
|
BranchId: BranchId,
|
|||
|
|
ProductDetails: ProductDetails,
|
|||
|
|
};
|
|||
|
|
try {
|
|||
|
|
let Response = await dispatch(postOpeningStock(PostData))?.unwrap();
|
|||
|
|
if (Response?.data?.statusCode === 1) {
|
|||
|
|
setMessageType('success');
|
|||
|
|
setMessageData('Stock added successfully');
|
|||
|
|
navigate(`${subDirectory}setting/opening-stock/`, {
|
|||
|
|
state: {
|
|||
|
|
Notiffy: {
|
|||
|
|
messageType: 'success',
|
|||
|
|
messageData: 'Stock added successfully',
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
setMessageType('error');
|
|||
|
|
setMessageData(
|
|||
|
|
Response?.data?.message || 'Failed to add opening stock'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.log(error, 'error');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const onComplete = useCallback(() => {
|
|||
|
|
setMessageData(null);
|
|||
|
|
setMessageType(null);
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (additionalInfoModal) {
|
|||
|
|
productAddInfoRef?.current?.setFieldsValue({
|
|||
|
|
ReceivedQty: selectedRowRecord?.ReceivedQty,
|
|||
|
|
RejectedQty: selectedRowRecord?.RejectedQty,
|
|||
|
|
AcceptedQty: selectedRowRecord?.AcceptedQty,
|
|||
|
|
FreeItem: selectedRowRecord?.FreeItem,
|
|||
|
|
OfferPrice: selectedRowRecord?.OfferPrice,
|
|||
|
|
WhSalePrice: selectedRowRecord?.WhSalePrice,
|
|||
|
|
SpecialPrice: selectedRowRecord?.SpecialPrice,
|
|||
|
|
MRP: selectedRowRecord?.MRP,
|
|||
|
|
SellPrice: selectedRowRecord?.SellPrice,
|
|||
|
|
AmountPerPiece: selectedRowRecord?.AmountPerPiece,
|
|||
|
|
NumberofPieceinside: selectedRowRecord?.NumberofPieceinside,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}, [additionalInfoModal]);
|
|||
|
|
|
|||
|
|
const handleMRPChange = (e) => {
|
|||
|
|
let value = e?.target?.value === '' ? 0 : parseFloat(e?.target?.value);
|
|||
|
|
productAddInfoRef?.current?.setFieldsValue({ SellPrice: null, MRP: value });
|
|||
|
|
setSelectedRowRecord((prev) => ({ ...prev, SellPrice: null, MRP: value }));
|
|||
|
|
productAddInfoRef?.current?.validateFields();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleAmountPerPiecechange = (e) => {
|
|||
|
|
const inputValue = e?.target?.value;
|
|||
|
|
|
|||
|
|
const amountPerPiece = inputValue === '' ? '' : parseFloat(inputValue);
|
|||
|
|
|
|||
|
|
if (selectedRowRecord?.OnePcsAvailable === 'Y') {
|
|||
|
|
setSelectedRowRecord((prev) => ({
|
|||
|
|
...prev,
|
|||
|
|
AmountPerPiece: amountPerPiece,
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleNumberofPieceinsidechange = (e) => {
|
|||
|
|
const inputValue = e?.target?.value;
|
|||
|
|
|
|||
|
|
const numberOfPieceInside = inputValue === '' ? '' : parseFloat(inputValue);
|
|||
|
|
|
|||
|
|
if (selectedRowRecord?.OnePcsAvailable === 'Y') {
|
|||
|
|
setSelectedRowRecord((prev) => ({
|
|||
|
|
...prev,
|
|||
|
|
NumberofPieceinside: numberOfPieceInside,
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleManufactureDate = (date, dateString) => {
|
|||
|
|
const formattedDate =
|
|||
|
|
dateString === ''
|
|||
|
|
? undefined
|
|||
|
|
: moment(dateString, ['DD-MM-YYYY']).format('YYYY-MM-DDTHH:mm:ss');
|
|||
|
|
productAddInfoRef?.current?.setFieldsValue({
|
|||
|
|
ManufDate: date,
|
|||
|
|
});
|
|||
|
|
setSelectedRowRecord((prev) => ({ ...prev, ManufDate: formattedDate }));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleBatchRefChange = (e) => {
|
|||
|
|
const value = parseInt(e?.target?.value);
|
|||
|
|
if (!/^\d*\.?\d*$/.test(value)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
productAddInfoRef?.current?.setFieldsValue({
|
|||
|
|
BatchRef: value,
|
|||
|
|
});
|
|||
|
|
setSelectedRowRecord((prev) => ({ ...prev, BatchRef: value }));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleModelNoChange = (e) => {
|
|||
|
|
const value = parseInt(e?.target?.value);
|
|||
|
|
if (!/^\d*\.?\d*$/.test(value)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
productAddInfoRef?.current?.setFieldsValue({
|
|||
|
|
ModelNumber: value,
|
|||
|
|
});
|
|||
|
|
setSelectedRowRecord((prev) => ({ ...prev, ModelNumber: value }));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleAddtnlDtlsSubmit = async (values) => {
|
|||
|
|
const totalQty = parseFloat(selectedRowRecord?.TotalQty) || 0;
|
|||
|
|
const currentQty = parseFloat(values?.Qty) || 0;
|
|||
|
|
const existingQtySum = additionalStockEntries.reduce(
|
|||
|
|
(sum, entry) => sum + (parseFloat(entry.Qty) || 0),
|
|||
|
|
0
|
|||
|
|
);
|
|||
|
|
const totalUsedQty = existingQtySum + currentQty;
|
|||
|
|
|
|||
|
|
if (totalUsedQty !== totalQty) {
|
|||
|
|
setMessageType('error');
|
|||
|
|
setMessageData(
|
|||
|
|
`Total quantity must equal ${totalQty}. Current total: ${totalUsedQty}`
|
|||
|
|
);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const newData = { ...values };
|
|||
|
|
setAdditionalStockEntries((prev) => [...prev, newData]);
|
|||
|
|
|
|||
|
|
const updatedProductStockData = filteredProductStockData.map((product) => {
|
|||
|
|
if (
|
|||
|
|
product.ProdId === selectedRowRecord.ProdId &&
|
|||
|
|
product.ProdVariantName === selectedRowRecord.ProdVariantName
|
|||
|
|
) {
|
|||
|
|
const details = [];
|
|||
|
|
|
|||
|
|
if (
|
|||
|
|
Array.isArray(additionalStockEntries) &&
|
|||
|
|
additionalStockEntries.length > 0
|
|||
|
|
) {
|
|||
|
|
details.push(...additionalStockEntries);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (newData?.Qty) {
|
|||
|
|
details.push(newData);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (details.length > 0) {
|
|||
|
|
return {
|
|||
|
|
...product,
|
|||
|
|
modalDetails: details,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return product;
|
|||
|
|
});
|
|||
|
|
setFilteredProductStockData(updatedProductStockData);
|
|||
|
|
setAdditionalInfoModal(false);
|
|||
|
|
setSelectedRowRecord({});
|
|||
|
|
setActiveAccordionKeys([]);
|
|||
|
|
await productAddInfoRef?.current?.resetFields();
|
|||
|
|
// await handleAdditionalInfoClose();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleAdditionalInfoClose = async () => {
|
|||
|
|
setAdditionalInfoModal(false);
|
|||
|
|
setSelectedRowRecord({});
|
|||
|
|
await productAddInfoRef?.current?.resetFields();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleAddNew = async () => {
|
|||
|
|
try {
|
|||
|
|
const values = await productAddInfoRef?.current?.validateFields();
|
|||
|
|
if (!values?.Qty) {
|
|||
|
|
setMessageType('error');
|
|||
|
|
setMessageData('Please enter Qty before adding new entry');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (!values?.MRP) {
|
|||
|
|
setMessageType('error');
|
|||
|
|
setMessageData('Please enter MRP before adding new entry');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (!values?.SellPrice) {
|
|||
|
|
setMessageType('error');
|
|||
|
|
setMessageData('Please enter Selling Price before adding new entry');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const newData = { ...selectedRowRecord, ...values };
|
|||
|
|
|
|||
|
|
const updatedEntries = [...additionalStockEntries, newData];
|
|||
|
|
setAdditionalStockEntries(updatedEntries);
|
|||
|
|
|
|||
|
|
// Update ProductStockData with modal details
|
|||
|
|
const updatedProductStockData = filteredProductStockData.map(
|
|||
|
|
(product) => {
|
|||
|
|
if (
|
|||
|
|
product.ProdId === selectedRowRecord.ProdId &&
|
|||
|
|
product.ProdVariantName === selectedRowRecord.ProdVariantName
|
|||
|
|
) {
|
|||
|
|
return {
|
|||
|
|
...product,
|
|||
|
|
modalDetails: updatedEntries,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
return product;
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
seProductStockData(updatedProductStockData);
|
|||
|
|
setFilteredProductStockData(
|
|||
|
|
updatedProductStockData.filter(
|
|||
|
|
(item) => !eliminatedProducts.includes(item.ProdId)
|
|||
|
|
)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Reset form for new entry
|
|||
|
|
setSelectedRowRecord({
|
|||
|
|
...selectedRowRecord,
|
|||
|
|
Qty: '',
|
|||
|
|
OnePieceQty: '',
|
|||
|
|
MRP: '',
|
|||
|
|
SellPrice: '',
|
|||
|
|
BatchRef: '',
|
|||
|
|
ModelNumber: '',
|
|||
|
|
ManufDate: '',
|
|||
|
|
AmountPerPiece: '',
|
|||
|
|
NumberofPieceinside: '',
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await productAddInfoRef?.current?.resetFields();
|
|||
|
|
|
|||
|
|
// Close all accordions after adding new entry
|
|||
|
|
setActiveAccordionKeys([]);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Validation failed:', error);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleEditEntry = (index) => {
|
|||
|
|
const entryToEdit = additionalStockEntries[index];
|
|||
|
|
setSelectedRowRecord((prev) => ({ ...prev, ...entryToEdit }));
|
|||
|
|
productAddInfoRef?.current?.setFieldsValue(entryToEdit);
|
|||
|
|
|
|||
|
|
setAdditionalStockEntries((prev) => prev.filter((_, i) => i !== index));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleDeleteEntry = (index) => {
|
|||
|
|
setAdditionalStockEntries((prev) => prev.filter((_, i) => i !== index));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="pageOverAll">
|
|||
|
|
<div className="userPage">
|
|||
|
|
<div className="userPageContent">
|
|||
|
|
<Messages
|
|||
|
|
messageType={messageType}
|
|||
|
|
messageData={messageData}
|
|||
|
|
onComplete={onComplete}
|
|||
|
|
/>
|
|||
|
|
<div className="purchase-entry-fh">
|
|||
|
|
<div className="formName">
|
|||
|
|
<FormHeader title={'Opening Stock Entry'} />
|
|||
|
|
{/* <div className="productMaster-input">
|
|||
|
|
<Buttons
|
|||
|
|
buttonText={ "Add New"}
|
|||
|
|
handleSubmit={() => handelAddButton()}
|
|||
|
|
color="901D77"
|
|||
|
|
icon={<PlusOutlined />}
|
|||
|
|
/>
|
|||
|
|
</div> */}
|
|||
|
|
{/* <div className="uploadexcelBtn">
|
|||
|
|
<Buttons
|
|||
|
|
buttonText="Upload Excel"
|
|||
|
|
className="tertiary_Button"
|
|||
|
|
handleSubmit={openModal}
|
|||
|
|
disabled={false}
|
|||
|
|
icon={<FiUploadCloud size={18} />}
|
|||
|
|
/>
|
|||
|
|
</div> */}
|
|||
|
|
<Tooltip title="Ignore Products">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setEliminateModal(true)}
|
|||
|
|
className="openingStock-ignoreProductBTN"
|
|||
|
|
>
|
|||
|
|
<IoBagRemove />
|
|||
|
|
{/* Ignore Products */}
|
|||
|
|
</button>
|
|||
|
|
</Tooltip>
|
|||
|
|
|
|||
|
|
{/* <div
|
|||
|
|
style={{
|
|||
|
|
display: 'flex',
|
|||
|
|
alignItems: 'center',
|
|||
|
|
gap: '7px',
|
|||
|
|
padding: '6px 12px',
|
|||
|
|
backgroundColor: '#059645',
|
|||
|
|
borderRadius: '8px',
|
|||
|
|
color: 'white',
|
|||
|
|
cursor: 'pointer',
|
|||
|
|
fontFamily: 'Poopies',
|
|||
|
|
fontWeight: '400',
|
|||
|
|
whiteSpace: "nowrap",
|
|||
|
|
fontSize: "15px"
|
|||
|
|
|
|||
|
|
}}
|
|||
|
|
onClick={() => navigate(`${subDirectory}setting/purchase-entry`)}
|
|||
|
|
>
|
|||
|
|
<FaEye /> <p> View Entry List</p>
|
|||
|
|
</div> */}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="formDiv">
|
|||
|
|
<Form
|
|||
|
|
ref={formRef}
|
|||
|
|
initialValues={editstate}
|
|||
|
|
className="formDivAnt"
|
|||
|
|
onFinish={onFinish}
|
|||
|
|
>
|
|||
|
|
<div className="purchase-entry-form">
|
|||
|
|
<div className="formDivS">
|
|||
|
|
<div className="inputForm">
|
|||
|
|
<div className="openingStock-search-input">
|
|||
|
|
<Input
|
|||
|
|
placeholder="Search products..."
|
|||
|
|
value={searchText}
|
|||
|
|
onChange={(e) => setSearchText(e.target.value)}
|
|||
|
|
className="openingStock-search-input-field"
|
|||
|
|
allowClear
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="Product-table openingStock-product-table">
|
|||
|
|
<style>
|
|||
|
|
{`
|
|||
|
|
.Product-table .ant-form-item-explain-error {
|
|||
|
|
display: none !important;
|
|||
|
|
}
|
|||
|
|
.Product-table .ant-form-item {
|
|||
|
|
margin-bottom: 0 !important;
|
|||
|
|
}
|
|||
|
|
`}
|
|||
|
|
</style>
|
|||
|
|
<Form form={form} component={false}>
|
|||
|
|
<table className="openingStock-table-wrapper">
|
|||
|
|
<thead className="openingStock-table-header">
|
|||
|
|
<tr className="openingStock-table-header-row">
|
|||
|
|
<th
|
|||
|
|
className="openingStock-table-header-cell"
|
|||
|
|
style={{ width: '20px' }}
|
|||
|
|
>
|
|||
|
|
S.NO
|
|||
|
|
</th>
|
|||
|
|
<th className="openingStock-table-header-cell-left">
|
|||
|
|
Product Name
|
|||
|
|
</th>
|
|||
|
|
<th className="openingStock-table-header-cell">
|
|||
|
|
Total Qty
|
|||
|
|
</th>
|
|||
|
|
<th className="openingStock-table-header-cell">
|
|||
|
|
Total Pieces
|
|||
|
|
</th>
|
|||
|
|
<th className="openingStock-table-header-cell">
|
|||
|
|
Additional Info
|
|||
|
|
</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
{filteredProductStockData
|
|||
|
|
.filter(
|
|||
|
|
(record) =>
|
|||
|
|
record.ProdName.toLowerCase().includes(
|
|||
|
|
searchText.toLowerCase()
|
|||
|
|
) ||
|
|||
|
|
record.ProdVariantName.toLowerCase().includes(
|
|||
|
|
searchText.toLowerCase()
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
.map((record, index) => (
|
|||
|
|
<tr
|
|||
|
|
key={index}
|
|||
|
|
className="openingStock-table-row"
|
|||
|
|
>
|
|||
|
|
<td className="openingStock-table-cell">
|
|||
|
|
{index + 1}
|
|||
|
|
</td>
|
|||
|
|
<td className="openingStock-table-cell-left">
|
|||
|
|
{record?.ProdName} (
|
|||
|
|
{record?.ProdVariantName})
|
|||
|
|
</td>
|
|||
|
|
<td className="openingStock-table-cell-input">
|
|||
|
|
<Input
|
|||
|
|
className="openingStock-input-field"
|
|||
|
|
value={record?.TotalQty ?? ''}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
handleQtyChange(e, record, index)
|
|||
|
|
}
|
|||
|
|
inputMode="decimal"
|
|||
|
|
onBlur={(e) =>
|
|||
|
|
handleQtyChange(e, record, index)
|
|||
|
|
}
|
|||
|
|
onInput={(e) => {
|
|||
|
|
let value = e.target.value.replace(
|
|||
|
|
/[^0-9.]/g,
|
|||
|
|
''
|
|||
|
|
);
|
|||
|
|
if (value.startsWith('.'))
|
|||
|
|
value = '0' + value;
|
|||
|
|
const dotCount = (
|
|||
|
|
value.match(/\./g) || []
|
|||
|
|
).length;
|
|||
|
|
if (dotCount > 1) {
|
|||
|
|
const firstDotIndex =
|
|||
|
|
value.indexOf('.');
|
|||
|
|
value =
|
|||
|
|
value.substring(
|
|||
|
|
0,
|
|||
|
|
firstDotIndex + 1
|
|||
|
|
) +
|
|||
|
|
value
|
|||
|
|
.substring(firstDotIndex + 1)
|
|||
|
|
.replace(/\./g, '');
|
|||
|
|
}
|
|||
|
|
e.target.value = value;
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</td>
|
|||
|
|
<td className="openingStock-table-cell-input">
|
|||
|
|
<Input
|
|||
|
|
className="openingStock-input-field"
|
|||
|
|
value={record?.TotalPiece ?? ''}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
handleTotalPieceChange(e, record, index)
|
|||
|
|
}
|
|||
|
|
inputMode="decimal"
|
|||
|
|
onBlur={(e) =>
|
|||
|
|
handleTotalPieceChange(e, record, index)
|
|||
|
|
}
|
|||
|
|
disabled={record?.OnePcsAvailable !== 'Y'}
|
|||
|
|
onInput={(e) => {
|
|||
|
|
let value = e.target.value.replace(
|
|||
|
|
/[^0-9.]/g,
|
|||
|
|
''
|
|||
|
|
);
|
|||
|
|
if (value.startsWith('.'))
|
|||
|
|
value = '0' + value;
|
|||
|
|
const dotCount = (
|
|||
|
|
value.match(/\./g) || []
|
|||
|
|
).length;
|
|||
|
|
if (dotCount > 1) {
|
|||
|
|
const firstDotIndex =
|
|||
|
|
value.indexOf('.');
|
|||
|
|
value =
|
|||
|
|
value.substring(
|
|||
|
|
0,
|
|||
|
|
firstDotIndex + 1
|
|||
|
|
) +
|
|||
|
|
value
|
|||
|
|
.substring(firstDotIndex + 1)
|
|||
|
|
.replace(/\./g, '');
|
|||
|
|
}
|
|||
|
|
e.target.value = value;
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</td>
|
|||
|
|
<td className="openingStock-table-cell">
|
|||
|
|
<IoAddCircleSharp
|
|||
|
|
className="openingStock-add-icon"
|
|||
|
|
onClick={(e) => {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
openAdditionalInfoModal(record, index);
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
))}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</Form>
|
|||
|
|
</div>
|
|||
|
|
{/* {
|
|||
|
|
PurchaseData?.length > 0 && <div className="purchase-status-data">
|
|||
|
|
<div className="total-purchase-amnt">
|
|||
|
|
<label>Total Amount: </label>
|
|||
|
|
<div>{PurchaseData.reduce((acc, data) => acc + data?.Amount, 0)}</div>
|
|||
|
|
</div>
|
|||
|
|
{!orderType &&
|
|||
|
|
<div className="purchase-status">
|
|||
|
|
<RadioGrpButton
|
|||
|
|
content={[
|
|||
|
|
{ value: "P", label: "Pending" },
|
|||
|
|
{ value: "C", label: "Completed" },
|
|||
|
|
]}
|
|||
|
|
Header={"Purchase Status"}
|
|||
|
|
defaultSelect={purchaseStatus}
|
|||
|
|
onSelectFuntion={purchaseStatusChange}
|
|||
|
|
/>
|
|||
|
|
</div>}
|
|||
|
|
</div>
|
|||
|
|
} */}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</Form>
|
|||
|
|
</div>
|
|||
|
|
<Form
|
|||
|
|
ref={formRef}
|
|||
|
|
initialValues={editstate}
|
|||
|
|
className="formDivAnt"
|
|||
|
|
onFinish={onFinish}
|
|||
|
|
>
|
|||
|
|
<div className="openingStock-submitButton">
|
|||
|
|
<Buttons
|
|||
|
|
buttonText="SUBMIT"
|
|||
|
|
color="901D77"
|
|||
|
|
icon={<ArrowRightOutlined />}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</Form>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<DefaultModal
|
|||
|
|
open={additionalInfoModal}
|
|||
|
|
className={'additional-dtls-modal'}
|
|||
|
|
footer={false}
|
|||
|
|
children={
|
|||
|
|
<>
|
|||
|
|
<div className="openingStock-modal-header">
|
|||
|
|
<h3>{`${selectedRowRecord?.ProdName} (${selectedRowRecord?.ProdVariantName})`}</h3>
|
|||
|
|
<button
|
|||
|
|
onClick={handleAddNew}
|
|||
|
|
className="openingStock-add-new-btn"
|
|||
|
|
>
|
|||
|
|
<PlusCircleOutlined />
|
|||
|
|
Add New
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<Form ref={productAddInfoRef} onFinish={handleAddtnlDtlsSubmit}>
|
|||
|
|
<div className="collapse-data">
|
|||
|
|
<div className="pricing-dtls">
|
|||
|
|
<Collapse
|
|||
|
|
activeKey={activeAccordionKeys}
|
|||
|
|
onChange={setActiveAccordionKeys}
|
|||
|
|
>
|
|||
|
|
<Panel header="Pricing Details" key="1">
|
|||
|
|
<div className="pricing-dtl-fields">
|
|||
|
|
<Form.Item name="Qty">
|
|||
|
|
<InputField
|
|||
|
|
autoComplete="off"
|
|||
|
|
label={
|
|||
|
|
<label className="required">
|
|||
|
|
Qty (Available:{' '}
|
|||
|
|
{(() => {
|
|||
|
|
const totalQty =
|
|||
|
|
parseFloat(
|
|||
|
|
selectedRowRecord?.TotalQty
|
|||
|
|
) || 0;
|
|||
|
|
const usedQty =
|
|||
|
|
additionalStockEntries.reduce(
|
|||
|
|
(sum, entry) =>
|
|||
|
|
sum + (parseFloat(entry.Qty) || 0),
|
|||
|
|
0
|
|||
|
|
);
|
|||
|
|
return totalQty - usedQty;
|
|||
|
|
})()}
|
|||
|
|
)
|
|||
|
|
</label>
|
|||
|
|
}
|
|||
|
|
isOnChange={
|
|||
|
|
selectedRowRecord?.Qty ? true : false
|
|||
|
|
}
|
|||
|
|
value={selectedRowRecord?.Qty}
|
|||
|
|
onChange={(e) => {
|
|||
|
|
const val = e?.target?.value;
|
|||
|
|
setSelectedRowRecord((prev) => ({
|
|||
|
|
...prev,
|
|||
|
|
Qty: val,
|
|||
|
|
}));
|
|||
|
|
}}
|
|||
|
|
inputMode="decimal"
|
|||
|
|
onInput={(e) => {
|
|||
|
|
let value = e.target.value.replace(
|
|||
|
|
/[^0-9.]/g,
|
|||
|
|
''
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (value.startsWith('.')) {
|
|||
|
|
value = '0' + value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const dotCount = (value.match(/\./g) || [])
|
|||
|
|
.length;
|
|||
|
|
if (dotCount > 1) {
|
|||
|
|
const firstDotIndex = value.indexOf('.');
|
|||
|
|
value =
|
|||
|
|
value.substring(0, firstDotIndex + 1) +
|
|||
|
|
value
|
|||
|
|
.substring(firstDotIndex + 1)
|
|||
|
|
.replace(/\./g, '');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
e.target.value = value;
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
<Form.Item name="MRP">
|
|||
|
|
<InputField
|
|||
|
|
autoComplete="off"
|
|||
|
|
label={<label>MRP</label>}
|
|||
|
|
isOnChange={
|
|||
|
|
selectedRowRecord?.MRP !== '' &&
|
|||
|
|
selectedRowRecord.hasOwnProperty('MRP')
|
|||
|
|
? true
|
|||
|
|
: false
|
|||
|
|
}
|
|||
|
|
value={selectedRowRecord?.MRP}
|
|||
|
|
onChange={handleMRPChange}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
<Form.Item name="SellPrice">
|
|||
|
|
<InputField
|
|||
|
|
autoComplete="off"
|
|||
|
|
label={<label>Sales Price</label>}
|
|||
|
|
isOnChange={
|
|||
|
|
selectedRowRecord?.SellPrice !== '' &&
|
|||
|
|
selectedRowRecord.hasOwnProperty('SellPrice')
|
|||
|
|
? true
|
|||
|
|
: false
|
|||
|
|
}
|
|||
|
|
value={selectedRowRecord?.SellPrice}
|
|||
|
|
onChange={(e) => {
|
|||
|
|
const val =
|
|||
|
|
e?.target?.value === ''
|
|||
|
|
? 0
|
|||
|
|
: parseFloat(e?.target?.value);
|
|||
|
|
setSelectedRowRecord((prev) => ({
|
|||
|
|
...prev,
|
|||
|
|
SellPrice: val,
|
|||
|
|
}));
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
{/* {selectedRowRecord?.OnePcsAvailable === "Y" && (
|
|||
|
|
<Form.Item name="OnePieceQty">
|
|||
|
|
<InputField
|
|||
|
|
autoComplete="off"
|
|||
|
|
label={<label>One Piece Qty</label>}
|
|||
|
|
isOnChange={
|
|||
|
|
selectedRowRecord?.OnePieceQty
|
|||
|
|
? true
|
|||
|
|
: false
|
|||
|
|
}
|
|||
|
|
value={selectedRowRecord?.OnePieceQty}
|
|||
|
|
onChange={(e) => {
|
|||
|
|
const val = e?.target?.value;
|
|||
|
|
setSelectedRowRecord((prev) => ({
|
|||
|
|
...prev,
|
|||
|
|
OnePieceQty: val,
|
|||
|
|
}));
|
|||
|
|
}}
|
|||
|
|
inputMode="decimal"
|
|||
|
|
onInput={(e) => {
|
|||
|
|
let value = e.target.value.replace(
|
|||
|
|
/[^0-9.]/g,
|
|||
|
|
"",
|
|||
|
|
);
|
|||
|
|
if (value.startsWith("."))
|
|||
|
|
value = "0" + value;
|
|||
|
|
const dotCount = (value.match(/\./g) || [])
|
|||
|
|
.length;
|
|||
|
|
if (dotCount > 1) {
|
|||
|
|
const firstDotIndex = value.indexOf(".");
|
|||
|
|
value =
|
|||
|
|
value.substring(0, firstDotIndex + 1) +
|
|||
|
|
value
|
|||
|
|
.substring(firstDotIndex + 1)
|
|||
|
|
.replace(/\./g, "");
|
|||
|
|
}
|
|||
|
|
e.target.value = value;
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
)} */}
|
|||
|
|
{selectedRowRecord?.OnePcsAvailable === 'Y' && (
|
|||
|
|
<>
|
|||
|
|
<Form.Item name="NumberofPieceinside">
|
|||
|
|
<InputField
|
|||
|
|
autoComplete="off"
|
|||
|
|
label={
|
|||
|
|
<label>Number of Piece Inside</label>
|
|||
|
|
}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
handleNumberofPieceinsidechange(e)
|
|||
|
|
}
|
|||
|
|
isOnChange={false}
|
|||
|
|
value={
|
|||
|
|
selectedRowRecord?.NumberofPieceinside
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
<Form.Item name="AmountPerPiece">
|
|||
|
|
<InputField
|
|||
|
|
autoComplete="off"
|
|||
|
|
label={<label>Amount / Piece</label>}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
handleAmountPerPiecechange(e)
|
|||
|
|
}
|
|||
|
|
isOnChange={false}
|
|||
|
|
value={selectedRowRecord?.AmountPerPiece}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</Panel>
|
|||
|
|
</Collapse>
|
|||
|
|
</div>
|
|||
|
|
{true && (
|
|||
|
|
<div className="pricing-dtls">
|
|||
|
|
<Collapse
|
|||
|
|
activeKey={
|
|||
|
|
activeAccordionKeys.includes('2') ? ['2'] : []
|
|||
|
|
}
|
|||
|
|
onChange={(keys) =>
|
|||
|
|
setActiveAccordionKeys((prev) =>
|
|||
|
|
keys.length > 0
|
|||
|
|
? [...prev.filter((k) => k !== '2'), '2']
|
|||
|
|
: prev.filter((k) => k !== '2')
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
<Panel header="Dates" key="2">
|
|||
|
|
<div className="pricing-dtl-fields">
|
|||
|
|
<Form.Item name="ManufDate">
|
|||
|
|
<div className="dates-and-expiry">
|
|||
|
|
<label>Entry Date</label>
|
|||
|
|
<DatePicProd
|
|||
|
|
canSelectPast={true}
|
|||
|
|
onPressEnter={(e) =>
|
|||
|
|
handleKeyPress(e, record, index)
|
|||
|
|
}
|
|||
|
|
onChange={handleManufactureDate}
|
|||
|
|
valueData={selectedRowRecord?.ManufDate}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</Form.Item>
|
|||
|
|
{/* <Form.Item name="ExpDate">
|
|||
|
|
<div className="dates-and-expiry">
|
|||
|
|
<label>Expire Date</label>
|
|||
|
|
<DatePicProd
|
|||
|
|
canSelectPast={false}
|
|||
|
|
onPressEnter={(e) => handleKeyPress(e, record, index)}
|
|||
|
|
onChange={handleExpireDate}
|
|||
|
|
valueData={selectedRowRecord?.ExpDate}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</Form.Item> */}
|
|||
|
|
</div>
|
|||
|
|
</Panel>
|
|||
|
|
</Collapse>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
{true && (
|
|||
|
|
<div className="pricing-dtls">
|
|||
|
|
<Collapse
|
|||
|
|
activeKey={
|
|||
|
|
activeAccordionKeys.includes('3') ? ['3'] : []
|
|||
|
|
}
|
|||
|
|
onChange={(keys) =>
|
|||
|
|
setActiveAccordionKeys((prev) =>
|
|||
|
|
keys.length > 0
|
|||
|
|
? [...prev.filter((k) => k !== '3'), '3']
|
|||
|
|
: prev.filter((k) => k !== '3')
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
>
|
|||
|
|
<Panel header="Batch & Model Details" key="3">
|
|||
|
|
<div className="pricing-dtl-fields">
|
|||
|
|
<Form.Item name="BatchRef">
|
|||
|
|
<InputField
|
|||
|
|
autoComplete="off"
|
|||
|
|
isOnChange={
|
|||
|
|
selectedRowRecord?.BatchRef &&
|
|||
|
|
selectedRowRecord?.BatchRef !== ''
|
|||
|
|
? true
|
|||
|
|
: false
|
|||
|
|
}
|
|||
|
|
value={selectedRowRecord?.BatchRef}
|
|||
|
|
label={'Batch No.'}
|
|||
|
|
onChange={handleBatchRefChange}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
<Form.Item name="ModelNumber">
|
|||
|
|
<InputField
|
|||
|
|
autoComplete="off"
|
|||
|
|
isOnChange={
|
|||
|
|
selectedRowRecord?.ModelNumber &&
|
|||
|
|
selectedRowRecord?.ModelNumber !== ''
|
|||
|
|
? true
|
|||
|
|
: false
|
|||
|
|
}
|
|||
|
|
value={selectedRowRecord?.ModelNumber}
|
|||
|
|
label={'Model No.'}
|
|||
|
|
onChange={handleModelNoChange}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
{/* <div className="model-data-add" onClick={handleModelDataOpen}>
|
|||
|
|
<IoAddCircleSharp size={18} />
|
|||
|
|
<span>Add Model Data</span>
|
|||
|
|
</div> */}
|
|||
|
|
</div>
|
|||
|
|
</Panel>
|
|||
|
|
</Collapse>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
{additionalStockEntries.length > 0 && (
|
|||
|
|
<div className="openingStock-entries-table">
|
|||
|
|
<h4>Added Entries:</h4>
|
|||
|
|
<Table
|
|||
|
|
size="small"
|
|||
|
|
dataSource={additionalStockEntries.map(
|
|||
|
|
(item, index) => ({ ...item, key: index })
|
|||
|
|
)}
|
|||
|
|
pagination={false}
|
|||
|
|
columns={[
|
|||
|
|
{ title: 'Qty', dataIndex: 'Qty', key: 'Qty' },
|
|||
|
|
{
|
|||
|
|
title: 'One Piece Qty',
|
|||
|
|
dataIndex: 'OnePieceQty',
|
|||
|
|
key: 'OnePieceQty',
|
|||
|
|
render: (value) => value || '-',
|
|||
|
|
},
|
|||
|
|
{ title: 'MRP', dataIndex: 'MRP', key: 'MRP' },
|
|||
|
|
{
|
|||
|
|
title: 'Sales Price',
|
|||
|
|
dataIndex: 'SellPrice',
|
|||
|
|
key: 'SellPrice',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: 'Batch No.',
|
|||
|
|
dataIndex: 'BatchRef',
|
|||
|
|
key: 'BatchRef',
|
|||
|
|
render: (value) => value || '-',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: 'Model No.',
|
|||
|
|
dataIndex: 'ModelNumber',
|
|||
|
|
key: 'ModelNumber',
|
|||
|
|
render: (value) => value || '-',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: 'Entry Date',
|
|||
|
|
dataIndex: 'ManufDate',
|
|||
|
|
key: 'ManufDate',
|
|||
|
|
render: (date) =>
|
|||
|
|
date ? date.format('DD-MM-YYYY') : '-',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: 'Action',
|
|||
|
|
key: 'action',
|
|||
|
|
render: (_, record, index) => (
|
|||
|
|
<div className="openingStock-entries-table-actions">
|
|||
|
|
<a>
|
|||
|
|
<EditFilled
|
|||
|
|
className="openingStock-edit-icon"
|
|||
|
|
onClick={() => {
|
|||
|
|
handleEditEntry(index);
|
|||
|
|
setActiveAccordionKeys(['1']);
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</a>
|
|||
|
|
<a
|
|||
|
|
onClick={(e) => {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<DeleteFilled
|
|||
|
|
className="openingStock-delete-icon"
|
|||
|
|
onClick={() => handleDeleteEntry(index)}
|
|||
|
|
/>
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
]}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
<div className="submitButton">
|
|||
|
|
<Buttons
|
|||
|
|
buttonText="SUBMIT"
|
|||
|
|
color="901D77"
|
|||
|
|
icon={<ArrowRightOutlined />}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</Form>
|
|||
|
|
</>
|
|||
|
|
}
|
|||
|
|
handleCancel={handleAdditionalInfoClose}
|
|||
|
|
buttonText="Submit"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<DefaultModal
|
|||
|
|
open={eliminateModal}
|
|||
|
|
title="Ignore Products"
|
|||
|
|
footer={false}
|
|||
|
|
children={
|
|||
|
|
<div>
|
|||
|
|
<div className="openingStock-modal-section">
|
|||
|
|
<label className="openingStock-modal-label">
|
|||
|
|
Select Products to Ignore:
|
|||
|
|
</label>
|
|||
|
|
<Select
|
|||
|
|
mode="multiple"
|
|||
|
|
placeholder="Choose products to ignore"
|
|||
|
|
className="openingStock-select-full-width"
|
|||
|
|
value={[]}
|
|||
|
|
onChange={(selectedIds) => {
|
|||
|
|
const newEliminated = [
|
|||
|
|
...eliminatedProducts,
|
|||
|
|
...selectedIds,
|
|||
|
|
];
|
|||
|
|
setEliminatedProducts(newEliminated);
|
|||
|
|
setFilteredProductStockData(
|
|||
|
|
ProductStockData.filter(
|
|||
|
|
(item) => !newEliminated.includes(item.ProdId)
|
|||
|
|
)
|
|||
|
|
);
|
|||
|
|
}}
|
|||
|
|
options={ProductStockData.filter(
|
|||
|
|
(product) => !eliminatedProducts.includes(product.ProdId)
|
|||
|
|
).map((product) => ({
|
|||
|
|
value: product.ProdId,
|
|||
|
|
label: `${product.ProdName} (${product.ProdVariantName})`,
|
|||
|
|
}))}
|
|||
|
|
showSearch
|
|||
|
|
filterOption={(input, option) =>
|
|||
|
|
option.label.toLowerCase().includes(input.toLowerCase())
|
|||
|
|
}
|
|||
|
|
maxTagCount={3}
|
|||
|
|
maxTagTextLength={30}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{eliminatedProducts.length > 0 && (
|
|||
|
|
<div className="openingStock-ignored-products-section">
|
|||
|
|
<h4>Ignored Products ({eliminatedProducts.length}):</h4>
|
|||
|
|
<div className="openingStock-ignored-products-container">
|
|||
|
|
{eliminatedProducts.map((prodId, index) => {
|
|||
|
|
const product = ProductStockData.find(
|
|||
|
|
(p) => p.ProdId === prodId
|
|||
|
|
);
|
|||
|
|
return (
|
|||
|
|
<div
|
|||
|
|
key={index}
|
|||
|
|
className="openingStock-ignored-product-tag"
|
|||
|
|
>
|
|||
|
|
<span className="openingStock-ignored-product-name">
|
|||
|
|
{product?.ProdName}
|
|||
|
|
</span>
|
|||
|
|
<span
|
|||
|
|
onClick={() => {
|
|||
|
|
const updatedEliminated =
|
|||
|
|
eliminatedProducts.filter(
|
|||
|
|
(id) => id !== prodId
|
|||
|
|
);
|
|||
|
|
setEliminatedProducts(updatedEliminated);
|
|||
|
|
setFilteredProductStockData(
|
|||
|
|
ProductStockData.filter(
|
|||
|
|
(item) =>
|
|||
|
|
!updatedEliminated.includes(item.ProdId)
|
|||
|
|
)
|
|||
|
|
);
|
|||
|
|
}}
|
|||
|
|
className="openingStock-ignored-product-remove"
|
|||
|
|
>
|
|||
|
|
×
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<div className="openingStock-modal-footer">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setEliminateModal(false)}
|
|||
|
|
className="openingStock-close-btn"
|
|||
|
|
>
|
|||
|
|
Close
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
}
|
|||
|
|
handleCancel={() => setEliminateModal(false)}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default openingStockForm;
|