Direct sales feature implementation and optimizations

This commit is contained in:
unknown 2026-02-12 15:46:52 +05:30
parent ee5a123e63
commit 43c95d1098
14 changed files with 1770 additions and 527 deletions

View File

@ -4,6 +4,7 @@ import {
axiosCommonInstanceData,
axiosRetailOcrData,
} from '../AuthenicationToken/AuthenticationToken';
import { data } from 'jquery';
// export const getUserGuideLines = createAsyncThunk('product/getBranchData', async ({ formType, userId }) => {
// if (formType !=null && formType !=undefined && userId !=null && userId !=undefined) {
@ -244,6 +245,22 @@ export const getProductDataPageNo = createAsyncThunk(
}
);
export const getDirectSaleProducts = createAsyncThunk(
'product/getDirectSaleProducts',
async (data) => {
const { CompId, BranchId, AppId, page } = data
if (
CompId != null && CompId != undefined &&
BranchId != null && BranchId != undefined &&
AppId != null && AppId != undefined
) {
return await axiosRetailInstanceData.get(
`/product?CompId=${CompId}&BranchId=${BranchId}&AppId=${AppId}&SalesType=DS&PageNumber=${page}`
);
}
}
);
export const getAllprodIds = createAsyncThunk(
'product/getAllprodIds',
async (ProdData) => {
@ -273,6 +290,13 @@ export const getAllprodIds = createAsyncThunk(
}
);
export const getAllProductShortCodes = createAsyncThunk('product/getAllProductShortCodes', async (data) => {
const { AppId, CompId, BranchId } = data;
if (AppId != null && AppId && BranchId != null && BranchId && AppId && AppId != null) {
return await axiosRetailInstanceData.get(`/Product/ProdShortCodes?CompId=${CompId}&BranchId=${BranchId}&AppId=${AppId}`);
}
});
export const getProductDataSearch = createAsyncThunk(
'product/getProductDataSearch',
async (ProdData) => {
@ -291,6 +315,22 @@ export const getProductDataSearch = createAsyncThunk(
}
);
export const getDirectSaleSearchedProduct = createAsyncThunk(
'product/getDirectSaleSearchedProduct',
async (data) => {
const { CompId, BranchId, AppId, prodName } = data;
if (
CompId != null && CompId != undefined &&
BranchId != null && BranchId != undefined &&
AppId != undefined && AppId != null
) {
return await axiosRetailInstanceData.get(
`/ProductMasterSearch?CompId=${CompId}&BranchId=${BranchId}&AppId=${AppId}&prodName=${prodName}&SalesType=DS`
);
}
}
);
export const getBrandData = createAsyncThunk(
'product/getBrandData',
async ({ ConfigId }) => {

View File

@ -26,21 +26,29 @@ export const getProductData = createAsyncThunk(
export const getInvoiceImageData = createAsyncThunk(
'Stock/getInvoiceImageData',
async (file) => {
async ({ file, AppId, CompId, BranchId }) => {
if (!file) return;
const formData = new FormData();
formData.append('file', file); // 👈 actual file here
formData.append('file', file); // file
formData.append('AppId', AppId); // 👈 add AppId
formData.append('CompId', CompId); // 👈 add CompId
formData.append('BranchId', BranchId); // 👈 add BranchId
const response = await axiosRetailOcrData.post('/invoice', formData, {
const response = await axiosRetailOcrData.post(
'/invoice',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
);
return response.data;
}
);
export const getPurchaseTypeData = createAsyncThunk(
'product/getPurchaseTypeData',
async () => {

View File

@ -358,6 +358,7 @@ const StandardTablePayment = () => {
const [creditCustomerOpen, setcreditCustomerOpen] = useState(false);
const [Splitpayment, setSplitpayment] = useState(false);
const [customerPreviesOrders, setCustomerPreviesOrders] = useState(false);
const paymentGatewayRef = React.useRef(null);
const [PrintOrderDetails, setPrintOrderDetails] = useState([]);
const [PaymentUpiOptions, setPaymentUpiOptions] = useState([]);
const [PaymentDeviceCard, setPaymentDeviceCard] = useState([]);
@ -4125,6 +4126,7 @@ const StandardTablePayment = () => {
children={
<>
<PaymentGatewayEmbedded
ref={paymentGatewayRef}
orderId={businessUPIOrderId}
setBusinessUPIOrderId={setBusinessUPIOrderId}
businessUPILink={businessUPILink}
@ -4142,6 +4144,7 @@ const StandardTablePayment = () => {
open={showCancelConfirm}
title="Cancel Payment"
onOk={() => {
paymentGatewayRef.current?.clearPolling();
setBusinessUPI(false);
setShowCancelConfirm(false);
ClearAllGlobalStateDatas();

View File

@ -18,6 +18,7 @@ import {
Switch,
Tooltip,
Popconfirm,
Tabs,
} from 'antd';
import { TiDelete } from 'react-icons/ti';
import { CiCalculator1 } from 'react-icons/ci';
@ -171,6 +172,7 @@ const FeaturesFunctionalities = (props) => {
const [WeightScale, setWeightScale] = useState(props.WeightScale);
const [WeightScale2, setWeightScale2] = useState(true);
const [activeTab, setActiveTab] = useState('quickAdd');
const [CusDetails, setCusDetails] = useState(false);
const [zipCodeData, setZipCodeData] = useState(false);
const [messageType, setMessageType] = useState(null);
@ -3904,17 +3906,27 @@ const FeaturesFunctionalities = (props) => {
<div>
<DefaultModal
open={props.QuickAdd}
width={800}
width={activeTab === 'quickAdd' ? 800 : 1000}
footer={false}
children={
// cardData Mohan 18-4-2025
<>
<Tabs
activeKey={activeTab}
onChange={setActiveTab}
items={[
{ key: 'quickAdd', label: 'Quick Add' },
{ key: 'directSale', label: 'Direct Sale' }
]}
/>
<ProductForm
formType="add"
activeTab={activeTab}
handleCancelData={props.handleQuickAddCancel}
ProductSearch={props.ProductSearch}
ProductSearchType={props.ProductSearchType}
cardData={props.cardData}
/>
</>
}
handleCancel={props.handleQuickAddCancel}
/>

View File

@ -1269,6 +1269,7 @@ function BSReprint({ setOpenModel = () => { } }) {
OverallDisc,
OrderPaymentDtl,
} = orderDtls;
const updated = productDetails?.map((item) => ({
...item,
Offer: item?.OfferAmt,

View File

@ -1,8 +1,8 @@
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef, useState, useImperativeHandle, forwardRef } from 'react';
import { useDispatch } from 'react-redux';
import { getPaymentStatusForBusinessUPI } from '../../../../Features/BookingScreen/BookingData/BookingData';
const PaymentGatewayEmbedded = ({
const PaymentGatewayEmbedded = forwardRef(({
orderId,
businessUPILink,
setBusinessUPI,
@ -12,12 +12,21 @@ const PaymentGatewayEmbedded = ({
ClearAllGlobalStateDatas = () => {},
CustomerDisplay = () => {},
Type=null
}) => {
}, ref) => {
const dispatch = useDispatch();
const intervalRef = useRef(null);
const [timedOut, setTimedOut] = useState(false);
const [showTimeout, setShowTimeout] = useState(false);
useImperativeHandle(ref, () => ({
clearPolling: () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}
}));
useEffect(() => {
if (paymentSucess || timedOut) return;
@ -174,6 +183,6 @@ const PaymentGatewayEmbedded = ({
)}
</>
);
};
});
export default PaymentGatewayEmbedded;

View File

@ -0,0 +1,901 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Table, Input, Select, Button } from 'antd';
import { PlusOutlined, DeleteOutlined, SaveOutlined, PrinterOutlined } from '@ant-design/icons';
import { Tables } from '../../../../Components/Tables/Table';
import { useDispatch } from 'react-redux';
import { postProductData, getDirectSaleProducts, getAllProductShortCodes, deleteProductData, getDirectSaleSearchedProduct } from '../../../../Features/ProductPage/ProductPage.js';
import { getSession, printDiv } from '../../../../Services/Others';
import { Messages } from '../../../../Components/Notifications/Messages.jsx';
import { FaStarOfLife } from "react-icons/fa6";
import { IoMdRefresh } from "react-icons/io";
import StickerPrintModel from '../../../Product/StickerPrintModal.jsx';
import { getPageStyle, style } from '../../../Product/Printstyles.js';
import { getBarcodeSessionsIDs, getBarcodeTemplate } from '../../../../Features/Barcode/Barcode.js';
import { generateBarcode, generateQRCode, generateQRCodeCopy } from '../../../../Services/utils.js';
import StickerPrintTemplates from '../../../Product/StickerTemplates.jsx';
import useBarcodeGenerator from './useBarcodeQRGenerator.js';
const DirectSale = ({ UomData = [], TaxData = [], ProductTypeData = [], ProdCatData = [], ProdSubCatData = [], SupplierData = [], activeTab = null }) => {
const formRef = useRef();
const tableRef = useRef();
const searchInputRef = useRef();
const dispatch = useDispatch();
const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const CompId = getSession('CompId');
const BranchId = getSession('BranchId');
const AppId = getSession('AppId');
const UserId = getSession('UserId');
const [dataSource, setDataSource] = useState([]);
const [editingKey, setEditingKey] = useState(null);
const [copies, setCopies] = useState(null);
const [dropdownValue, setDropdownValue] = useState(null);
const [multiProduct, setMultiProduct] = useState(false);
const [printReady, setPrintReady] = useState(false);
const [printProductName, setPrintProductName] = useState(false);
const [productShortCodes, setAllProductShortCodes] = useState([]);
const [searchText, setSearchText] = useState('');
console.log(searchText, "searchText")
const [stickerPrintModalOpen, setStickerPrintModalOpen] = useState(false);
const [barcodeTemplateDetails, setBarcodeTemplateDetails] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
const [labelPrintData, setLabelPrintData] = useState(null);
const [prodId, setProdId] = useState(null);
const [nickName, setNickName] = useState(null);
const [crossData, setCrossData] = useState(null);
const [triggerState, setTriggerState] = useState(1);
const [templateOptions, setTemplateOptions] = useState({
mrp: false,
barcode: false,
color: false,
eDate: false,
mDate: false,
sellingPrice: false,
productName: false,
value: '',
secondValue: '',
qrcode: false,
codeType: '',
});
console.log((searchText != null && searchText != ''), "filteredProducts")
const { qrBase64, barcodeBase64 } = useBarcodeGenerator(prodId);
const handleAdd = () => {
setDataSource([{
key: dataSource.length + 1,
productName: '',
shortCode: '',
sellingPrice: '',
uom: null,
tax: null,
prodId: null,
size: null,
uomName: null,
activeStatus: 'A'
}, ...dataSource]);
};
const handleDelete = async (record) => {
if (record?.prodId) {
const deleteData = {
ProdId: record.prodId,
ActiveStatus: record.activeStatus === 'A' ? 'D' : 'A',
UpdatedBy: UserId,
};
const response = await dispatch(deleteProductData(deleteData))?.unwrap();
if (response?.data?.statusCode === 1) {
setMessageData(record.activeStatus === 'A' ? 'Product De-Activated Successfully' : 'Product Activated Successfully');
setMessageType('success');
await fetchDirectSaleProducts();
} else {
setMessageData(response?.data?.response || 'Failed to update status');
setMessageType('error');
}
return;
} else {
// const { key } = record;
// setDataSource(dataSource.filter(item => item.key !== key));
setMessageData('Atleast one empty row should be in table.');
setMessageType('warning');
}
};
const handleChange = useCallback((key, field, value) => {
if (searchText) {
setFilteredProducts(prev =>
prev.map(item =>
item.key === key ? { ...item, [field]: value } : item
)
);
} else {
setDataSource(prev =>
prev.map(item =>
item.key === key ? { ...item, [field]: value } : item
)
);
}
}, [searchText]);
const handleSave = async (record) => {
if (!record.productName || !record.sellingPrice || !record.uom || !record.tax || !record?.shortCode) {
setMessageData('Please fill all the required fields!');
setMessageType('warning');
return;
}
const exists = isShortCodeDuplicate(record?.shortCode, record?.prodId);
if (exists) {
setMessageData('Short code already exists for another product');
setMessageType('error');
return;
}
const postData = {
ProdName: record.productName,
Size: '1',
UOM: record.uom,
MRP: record.sellingPrice,
SellPrice: record.sellingPrice,
TaxId: record.tax,
CompId: CompId,
BranchId: BranchId,
AppId: AppId,
ProdType: ProductTypeData?.find(item => item.ConfigName === 'Product')?.ConfigId,
ProdCat: ProdCatData?.find(item => item.ConfigName === 'General')?.ConfigId,
ProdSubCat: ProdSubCatData?.find(item => item.ConfigName === 'General')?.ConfigId,
QtyBasedPrice: 'N',
ProdQtywisePriceDetails: [],
StockAvailable: 'N',
TokenAvailable: 'N',
InwardDate: new Date().toJSON(),
SuppId: SupplierData?.find(item => item.SuppName?.toLowerCase() === 'self')?.SuppId,
AutoGenerateQr: 'N',
QRCode: null,
CreatedBy: UserId,
ProdLogo: '',
Cess: 0,
ProdShortCode: record?.shortCode
};
try {
if (record?.prodId) {
console.log('Updating Product')
} else {
const response = await dispatch(postProductData(postData)).unwrap();
if (response?.data?.statusCode === 1) {
const prodId = response?.data?.ProductDetails?.[0]?.ProdId;
const size = response?.data?.ProductDetails?.[0]?.Size;
const uomName = response?.data?.ProductDetails?.[0]?.UomName;
const totalProducts = response?.data?.ProductDetails?.[0]?.TotalCount;
setSearchText(currentSearchText => {
if (currentSearchText != null && currentSearchText !== '') {
setCurrentPage(1);
setTriggerState(prev => prev + 1);
if (searchInputRef.current) {
searchInputRef.current.value = '';
}
return '';
} else {
setDataSource(prev => prev.map(item =>
item.key === record.key ? { ...item, prodId, size, uomName, totalProducts } : { ...item, totalProducts }
));
}
return currentSearchText;
});
setMessageData('Product saved successfully!');
setMessageType('success');
return;
}
}
} catch (error) {
console.error('Error saving product:', error);
}
};
const handlePrint = async (record) => {
const exists = isShortCodeDuplicate(record?.shortCode, record?.prodId);
if (exists) {
setMessageData('Short code already exists for another product. Cannot print.');
setMessageType('error');
return;
}
setStickerPrintModalOpen(true);
setLabelPrintData({ ...record, MRP: record?.sellingPrice || 0, SellPrice: record?.sellingPrice || 0 });
setProdId(record?.shortCode + '' + record?.sellingPrice)
}
const columns = useMemo(() => [
{
title: 'SL.No',
width: 50,
align: 'center',
render: (_, __, index) => (currentPage - 1) * 10 + index + 1
},
{
title: () => <div style={{ textWrap: 'nowrap', display: "flex", gap: "4px " }}>Product Name
<span>
<FaStarOfLife size={10} color='red' className='requiredIcons' />
</span>
</div>,
dataIndex: 'productName',
width: 150,
render: (text, record) => (
record.prodId && editingKey !== record.key ?
<div>{text}</div> :
<Input
value={text}
onChange={(e) => handleChange(record.key, 'productName', e.target.value)}
placeholder="Enter product name"
/>
)
},
{
title: () => <div style={{ textWrap: 'nowrap', display: "flex", gap: "4px " }}>Short Code
<span>
<FaStarOfLife size={10} color='red' className='requiredIcons' />
</span>
</div>,
dataIndex: 'shortCode',
width: 100,
render: (text, record) => (
record.prodId && editingKey !== record.key ?
<div>{text}</div> :
<Input
value={text}
onInput={(e) => {
const originalValue = e.target.value;
const hasNonAlpha = /[^a-zA-Z]/.test(originalValue);
if (hasNonAlpha) {
setMessageData('Enter Alphabet Characters only');
setMessageType('warning');
}
}}
onChange={(e) => {
const value = e.target.value.replace(/[^a-zA-Z]/g, '').toUpperCase();
const exists = isShortCodeDuplicate(value, record?.prodId);
if (exists && value) {
setMessageData('Short code already exists for another product');
setMessageType('error');
}
handleChange(record.key, 'shortCode', value);
}}
placeholder="Short Code"
/>
)
},
{
title: () => <div style={{ textWrap: 'nowrap', display: "flex", gap: "4px " }}>Selling Price
<span>
<FaStarOfLife size={10} color='red' className='requiredIcons' />
</span>
</div>,
dataIndex: 'sellingPrice',
width: 120,
render: (text, record) => (
record.prodId && editingKey !== record.key ?
<div>{text}</div> :
<Input
value={text}
onChange={(e) => {
const value = e.target.value.replace(/[^0-9.]/g, '');
const parts = value.split('.');
const formatted = parts.length > 2 ? `${parts[0]}.${parts.slice(1).join('')}` : value;
handleChange(record.key, 'sellingPrice', formatted);
}}
placeholder="0.00"
/>
)
},
{
title: () => <div style={{ textWrap: 'nowrap', display: "flex", gap: "4px " }}>UOM
<span>
<FaStarOfLife size={10} color='red' className='requiredIcons' />
</span>
</div>,
dataIndex: 'uom',
width: 60,
render: (text, record) => (
record.prodId && editingKey !== record.key ?
<div>{UomData?.find(item => item.ConfigId === text)?.ConfigName || '-'}</div> :
<Select
disabled={record?.prodId}
className='uom-select'
value={text}
onChange={(value) => handleChange(record.key, 'uom', value)}
placeholder="UOM"
style={{ width: '100%' }}
options={UomData?.map(item => ({
value: item.ConfigId,
label: item.ConfigName
}))}
/>
)
},
{
title: () => <div style={{ textWrap: 'nowrap', display: "flex", gap: "4px " }}>Tax
<span>
<FaStarOfLife size={10} color='red' className='requiredIcons' />
</span>
</div>,
dataIndex: 'tax',
width: 70,
render: (text, record) => {
if (record.prodId && editingKey !== record.key) {
const tax = TaxData?.find(item => item.TaxId === text);
return <div>{tax ? tax?.TaxIdName + ' - ' + `${tax?.TaxPercentage}%` : '-'}</div>;
}
return (
<Select
className='tax-select'
value={text}
disabled={record?.prodId}
onChange={(value) => handleChange(record.key, 'tax', value)}
placeholder="Tax"
style={{ width: '100%' }}
options={TaxData?.map(item => ({
value: item.TaxId,
label: `${item.TaxIdName} - ${item.TaxPercentage}%`
}))}
/>
);
}
},
{
title: 'Save / Print',
width: 90,
align: 'center',
render: (_, record) => {
const existingProduct = record?.prodId;
return (
<div className='directsaleBTN' style={{ display: 'flex', gap: '8px', justifyContent: 'center' }}>
{!existingProduct && <Button
icon={<SaveOutlined />}
size="small"
disabled={existingProduct}
className='save-btn'
onClick={() => handleSave(record)}
>
Save
{/* {!existingProduct ? 'Save' : 'Update'} */}
</Button>}
{existingProduct && <Button
icon={<PrinterOutlined />}
size="small"
disabled={!existingProduct}
onClick={() => handlePrint(record)}
>
Print
</Button>}
</div>
);
}
},
{
title: 'Action',
width: 60,
align: "center",
render: (_, record) => {
if (record?.activeStatus === 'A') {
return <DeleteOutlined
onClick={() => handleDelete(record)}
style={{ color: 'red', cursor: 'pointer', fontSize: '18px' }}
/>
} else {
return <IoMdRefresh
style={{ color: 'blue', cursor: 'pointer', fontSize: '18px' }}
onClick={() => handleDelete(record)}
/>
}
}
}
], [
searchText,
editingKey,
productShortCodes,
UomData,
TaxData
]);
useEffect(() => {
if (activeTab === 'directSale' && AppId && CompId && BranchId) {
fetchDirectSaleProducts();
fetchAllProductShortCodes();
}
}, [activeTab, AppId, BranchId, CompId, currentPage, triggerState]);
useEffect(() => {
if (dataSource.every(row => row.prodId)) {
handleAdd();
}
}, [dataSource]);
useEffect(() => {
const timer = setTimeout(() => {
if (searchText) {
fetchSearchedProducts();
} else {
setFilteredProducts([{
key: 1,
productName: '',
shortCode: '',
sellingPrice: '',
uom: null,
tax: null,
prodId: null,
size: null,
uomName: null,
activeStatus: 'A'
}]);
}
}, 500);
return () => clearTimeout(timer);
}, [searchText, AppId, BranchId, CompId]);
useEffect(() => {
const handleClickOutside = (event) => {
if (tableRef.current && !tableRef.current.contains(event.target) && !event.target.closest('.ant-select-dropdown')) {
setEditingKey(null);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
useEffect(() => {
if (activeTab === 'directSale' && AppId && CompId && BranchId && stickerPrintModalOpen) {
getBarcodeSessionData();
fetchBarcodeTemplateDetails();
}
}, [activeTab, AppId, BranchId, CompId, stickerPrintModalOpen]);
const fetchSearchedProducts = async () => {
try {
const data = {
AppId: AppId,
CompId: CompId,
BranchId: BranchId,
prodName: searchText
}
const response = await dispatch(getDirectSaleSearchedProduct(data))?.unwrap();
if (response?.data?.data?.length > 0 && response?.data?.statusCode === 1) {
const products = response?.data?.data?.map((item, index) => ({
key: index + 1,
productName: item.ProdName,
shortCode: item.ProdShortCode,
sellingPrice: item.SellPrice,
uom: item.UOM,
tax: item.TaxId,
prodId: item.ProdId,
activeStatus: item?.ActiveStatus,
size: item.Size || 1,
uomName: item.UomName || 'PCS',
totalProducts: item?.TotalCount
}))
setFilteredProducts([{
key: products.length + 1,
productName: '',
shortCode: '',
sellingPrice: '',
uom: null,
tax: null,
prodId: null,
size: null,
uomName: null,
activeStatus: 'A'
}, ...products]);
} else {
setFilteredProducts([{
key: 1,
productName: '',
shortCode: '',
sellingPrice: '',
uom: null,
tax: null,
prodId: null,
size: null,
uomName: null,
activeStatus: 'A'
}]);
}
} catch (error) {
console.error(error?.message)
}
}
const isShortCodeDuplicate = useCallback((code, prodId) => {
return productShortCodes.some(
item =>
item.ProdShortCode.toUpperCase() === code.toUpperCase() &&
item.ProdId !== prodId
);
}, [productShortCodes]);
const fetchBarcodeTemplateDetails = async () => {
const response = await dispatch(
getBarcodeTemplate({ compId: CompId, appId: AppId, branchId: BranchId })
).unwrap();
if (response?.data?.statusCode === 1 && response?.data?.data?.length > 0) {
const { TemplateDetails } = response?.data?.data?.[0];
setBarcodeTemplateDetails(TemplateDetails);
} else {
setBarcodeTemplateDetails([]);
}
};
const getBarcodeSessionData = async () => {
try {
let response = await dispatch(getBarcodeSessionsIDs()).unwrap();
if (response?.data?.statusCode === 1 && response?.data?.data?.length > 0) {
setCrossData(
response?.data?.data?.filter(
(item) => !item.ConfigName?.includes('100X13 (55MM Printable Gold)')
) || []
);
} else {
setCrossData([]);
}
} catch (error) {
console.error('Error fetching barcode session data:', error);
}
};
const fetchDirectSaleProducts = async () => {
try {
const data = {
AppId: AppId,
CompId: CompId,
BranchId: BranchId,
page: currentPage
}
const response = await dispatch(getDirectSaleProducts(data))?.unwrap();
if (response?.data?.statusCode === 1 && response?.data?.data?.length > 0) {
const products = response?.data?.data?.map((item, index) => ({
key: index + 1,
productName: item.ProdName,
shortCode: item.ProdShortCode,
sellingPrice: item.SellPrice,
uom: item.UOM,
tax: item.TaxId,
prodId: item.ProdId,
activeStatus: item?.ActiveStatus,
size: item.Size || 1,
uomName: item.UomName || 'PCS',
totalProducts: item?.TotalCount
}));
setDataSource([{
key: products.length + 1,
productName: '',
shortCode: '',
sellingPrice: '',
uom: null,
tax: null,
prodId: null,
size: null,
uomName: null,
activeStatus: 'A'
}, ...products]);
} else {
setDataSource([])
}
} catch (error) {
console.error('Error fetching direct sale products:', error);
}
}
const fetchAllProductShortCodes = async () => {
try {
const data = {
AppId: AppId,
CompId: CompId,
BranchId: BranchId,
}
const response = await dispatch(getAllProductShortCodes(data))?.unwrap();
if (response?.data?.statusCode === 1 && response?.data?.data?.length > 0) {
setAllProductShortCodes(response?.data?.data);
} else {
setAllProductShortCodes([]);
}
console.log(response);
} catch (error) {
console.error('Error fetching product short codes:', error);
}
}
const handlePageChange = (current) => {
setCurrentPage(current);
};
const onFinish = async () => {
const template = barcodeTemplateDetails?.find(
(find) => find.SessionName === dropdownValue
);
const templateAvailable = template === undefined ? false : true;
if (templateAvailable) {
const checkComponent = (name) =>
template?.OptionDetails?.some((some) => some.OptionName === name);
const mrp = checkComponent('MRP');
const barcode = checkComponent('Barcode');
const color = checkComponent('Color');
const eDate = checkComponent('E Date');
const mDate = checkComponent('M Date');
const sellingPrice = checkComponent('Selling Price');
const productName = checkComponent('Product Name');
const qrcode = checkComponent('Qrcode');
const position = template.Position !== '' ? template.Position : '';
const positionString =
template.PositionString !== '' ? template.PositionString : '';
setTemplateOptions({
mrp: mrp,
barcode: barcode,
color: color,
eDate: eDate,
mDate: mDate,
sellingPrice: sellingPrice,
productName: productName,
value: position,
secondValue: positionString,
qrcode: qrcode,
codeType: template?.CodeType,
});
}
setPrintReady(true);
setTimeout(async () => {
switch (dropdownValue) {
case '15X15 6cross': // COMPLETE
await printDiv(
templateAvailable ? 'label-15X156cross' : 'StickerPrints0',
style + getPageStyle(92)
);
handleModalClose();
break;
case '22X35 3cross': // Need to check
await printDiv(
templateAvailable ? '22X353cross' : 'StickerPrints1',
style + getPageStyle(105)
);
handleModalClose();
break;
case '25X25 4cross': // COMPLETE
await printDiv(
templateAvailable ? '25X254cross' : 'StickerPrints2',
style + getPageStyle(100)
);
handleModalClose();
break;
case '25X50 2cross': //Need to check
await printDiv(
templateAvailable ? '25X502cross' : 'StickerPrints3',
style + getPageStyle(100)
);
handleModalClose();
break;
case '50X30 Single': // COMPLETE
await printDiv(
templateAvailable ? '50X30Single' : 'StickerPrints4',
style
);
handleModalClose();
break;
case '50X25 Single': // COMPLETE
await printDiv(
templateAvailable ? '50X25Single' : 'StickerPrints5',
style
);
handleModalClose();
break;
case '100X13 (55MM Printable)': //Need to check
await printDiv(
templateAvailable ? '100X13(55MMPrintable)' : 'StickerPrints6',
style
);
handleModalClose();
break;
case '100X15 (70MM Printable)': // COMPLETE
await printDiv(
templateAvailable ? '100X15(70MMPrintable)' : 'StickerPrints7',
style
);
handleModalClose();
break;
case '100X150': // COMPLETE
await printDiv(
templateAvailable ? '100X150' : 'StickerPrints8',
style
);
handleModalClose();
setPrintReady(false);
break;
default:
alert('! Select correct cross ');
setPrintReady(false);
}
}, 100);
};
const CoresOnChange = (params) => {
setDropdownValue(params);
formRef.current?.setFieldsValue({ Cross: params });
const template = barcodeTemplateDetails?.find(
(find) => find.SessionName === params
);
const checkComponent = (name) =>
template?.OptionDetails?.some((some) => some.OptionName === name);
const productName = checkComponent('Product Name');
setPrintProductName(productName);
};
const CopiesOnChange = (params) => {
setCopies(params);
formRef.current?.setFieldsValue({ Copies: params });
};
const handleModalClose = () => {
setCopies(null)
setDropdownValue('')
setStickerPrintModalOpen(false);
setLabelPrintData(null);
setNickName(null);
setProdId(null)
setMultiProduct(false);
formRef?.current?.resetFields();
setPrintReady(false);
};
const imageTagBarcodeAndQR = useCallback(
(w = 'auto', fs = '6px', codeData = null) => {
const currentCode = codeData || prodId;
return (
(templateOptions.barcode || templateOptions.qrcode) && (
<>
<img
style={{ width: w }}
src={
templateOptions.codeType === 'B'
? !multiProduct
? barcodeBase64
: generateBarcode(currentCode)
: !multiProduct
? qrBase64
: generateQRCode(QrandbarcodeDatas, currentCode)
}
alt="Barcode"
/>
{templateOptions.codeType !== 'B' && (
<p style={{ margin: '0', padding: '0', fontSize: fs }}>
{currentCode}
</p>
)}
</>
)
);
},
[templateOptions, multiProduct, barcodeBase64, qrBase64, prodId]
);
const onComplete = useCallback(() => {
setMessageType(null);
setMessageData(null);
}, []);
return (
<div className='direct-sale-container'>
<Messages
messageType={messageType}
messageData={messageData}
onComplete={onComplete}
/>
<div className="bsreprintmodaltable-search">
<input
ref={searchInputRef}
type="text"
placeholder="Search Product / Short Code..."
onChange={(e) => setSearchText(e.target.value)}
/>
</div>
<div style={{ marginBottom: '10px', color: '#666', fontSize: '14px', fontStyle: 'italic' }}>
Note: Click on a row to modify the product details
</div>
<div className='DirectSaleTable' ref={tableRef}>
<Tables
rowKey="prodId"
data={(searchText != null && searchText != '') ? filteredProducts : dataSource}
dataSource={(searchText != null && searchText != '') ? filteredProducts : dataSource}
columns={columns}
onRow={(record) => ({
onClick: () => record.prodId && setEditingKey(record.key)
})}
ownPagination={true}
pagination={{
current: currentPage,
onChange: handlePageChange,
total: (searchText != null && searchText != '') ? filteredProducts?.length : ((dataSource[dataSource.length - 1]?.totalProducts || 0) + 1),
pageSize: 11,
showSizeChanger: false,
hideOnSinglePage: true,
}}
bordered
/>
</div>
{stickerPrintModalOpen && <StickerPrintModel
open={stickerPrintModalOpen}
handleCancel={handleModalClose}
formRef={formRef}
onFinish={onFinish}
MultiCode={multiProduct}
activeTab={null}
setActiveTab={() => { }}
selectedRecords={null}
coresdata={crossData}
dropdownValue={dropdownValue}
CoresOnChange={CoresOnChange}
barcodeTemplateDetails={barcodeTemplateDetails}
detail={labelPrintData}
proName={labelPrintData?.productName}
nickName={nickName}
ProdId={labelPrintData?.shortCode + '' + labelPrintData?.sellingPrice}
size={labelPrintData?.size}
// valueStyleColumn={valueStyleColumn}
// secondValueStyle={secondValueStyle}
// valueStyleRow={valueStyleRow}
CopiesOnChange={CopiesOnChange}
printProductName={printProductName}
totalCopies={null}
setstockWiseQrcode={null}
setProductWiseQrcode={null}
setNickName={setNickName}
templateOptions={templateOptions}
/>}
{printReady && (
<StickerPrintTemplates
selectedRecords={null}
copies={copies}
nickName={nickName}
proName={labelPrintData?.productName}
ProdId={labelPrintData?.shortCode + '' + labelPrintData?.sellingPrice}
size={labelPrintData?.size + ' ' + labelPrintData?.uomName}
detail={labelPrintData}
templateOptions={templateOptions}
base64Image={qrBase64}
QrandbarcodeDatas={null}
MultiCode={multiProduct}
generateQRCodeCopy={(code) =>
generateQRCodeCopy(null, code)
}
generateCodeImage={(code, codeType = 'Q') => generateCodeImage(code, codeType, null)}
imageTagBarcodeAndQR={imageTagBarcodeAndQR}
dropdownValue={dropdownValue}
barcodeTemplateDetails={barcodeTemplateDetails}
/>
)}
</div>
);
};
export default DirectSale;

View File

@ -1,6 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Form, Tooltip } from 'antd';
import { Form, Tooltip, Tabs } from 'antd';
import DirectSale from './DirectSale.jsx';
import {
ArrowRightOutlined,
PlusCircleOutlined,
@ -13,7 +14,7 @@ import { Messages } from '../../../../Components/Notifications/Messages';
import { DropDowns } from '../../../../Components/Forms/DropDown.jsx';
import { DatePic } from '../../../../Components/Forms/DatePicker.jsx';
import { DefaultModal } from '../../../../Components/Modal/DefaultModal.jsx';
import FormHeader from '../../../PageComponents/FormHeader.jsx';
import { getSession, validateSafeInput } from '../../../../Services/Others';
import {
getAdmin,
@ -61,6 +62,7 @@ const ProductForm = ({
ProductSearch,
ProductSearchType,
cardData,
activeTab = null
}) => {
const dispatch = useDispatch();
const formRef = useRef(null);
@ -116,6 +118,7 @@ const ProductForm = ({
const [tempSelectedColumns, setTempSelectedColumns] = useState([]);
const [tableFieldPreferences, setTableFieldPreferences] = useState([]);
const [selectedFields, setSelectedFields] = useState([]);
console.log(selectedAdditionalColumn, "selectedAdditionalColumn")
console.log(tempSelectedColumns, "tempSelectedColumns")
const CompId = getSession('CompId');
@ -515,9 +518,18 @@ const ProductForm = ({
messageData={messageData}
onComplete={onComplete}
/>
<div className="formName">
<FormHeader title={'Quick Add'} />
</div>
{activeTab === 'directSale' ? (
<DirectSale
UomData={UomPreference}
TaxData={TaxData}
ProductTypeData={ProductTypeData}
ProdCatData={ProdCatData}
ProdSubCatData={ProdSubCatData}
SupplierData={SupplierData}
activeTab={activeTab}
/>
) : (
<div>
<div style={{
@ -1040,6 +1052,7 @@ const ProductForm = ({
</DefaultModal>
</div>
)}
</div>
);
};

View File

@ -0,0 +1,69 @@
import { useEffect, useState } from 'react';
import QRCode from 'qrcode';
import JsBarcode from 'jsbarcode';
const useBarcodeGenerator = (code) => {
const [qrBase64, setQrBase64] = useState(null);
const [barcodeBase64, setBarcodeBase64] = useState(null);
useEffect(() => {
if (!code) {
setQrBase64(null);
return;
}
const generateQR = async () => {
try {
const url = await QRCode.toDataURL(code);
setQrBase64(url);
} catch (error) {
console.error('QR generation error:', error);
}
};
generateQR();
}, [code]);
useEffect(() => {
if (!code) {
setBarcodeBase64(null);
return;
}
try {
const svg = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg'
);
JsBarcode(svg, code, {
format: 'CODE128',
displayValue: true,
lineColor: '#000',
width: 2,
height: 50,
});
const svgData = new XMLSerializer().serializeToString(svg);
const svgBlob = new Blob([svgData], {
type: 'image/svg+xml;charset=utf-8',
});
const reader = new FileReader();
reader.onloadend = () => {
setBarcodeBase64(reader.result);
};
reader.readAsDataURL(svgBlob);
} catch (error) {
console.error('Barcode generation error:', error);
}
}, [code]);
return {
qrBase64,
barcodeBase64
};
};
export default useBarcodeGenerator;

View File

@ -87,7 +87,7 @@ const StickerPrintModel = ({
if (coresdata?.length > 0 && open) {
CoresOnChange(coresdata?.[0].ConfigName);
}
}, [open]);
}, [open, coresdata]);
return (
<DefaultModal
width={1000}

View File

@ -36,6 +36,7 @@ import { Tables } from '../../Components/Tables/Table.jsx';
import { InputField } from '../../Components/Forms/InputField.jsx';
import TextAreaInput from '../../Components/Forms/TextArea.jsx';
import { uploadImage } from '../../Features/upload/upload.js';
import { getSession } from '../../Services/Others.js';
const { Option } = Select;
const { Column } = Table;
@ -490,6 +491,11 @@ const InvoiceImageExtractorModal = ({
uomData = [],
setVisible = () => { },
}) => {
const AppId = getSession('AppId');
const CompId = getSession('CompId');
const BranchId = getSession('BranchId');
const extractedTableRef = useRef(null);
const [form] = Form.useForm();
const [ocrLoading, setOcrLoading] = useState(false);
@ -503,7 +509,7 @@ const InvoiceImageExtractorModal = ({
const [productTableData, setProductTableData] = useState([]);
const [TotalAmtData, SetTotalAmtData] = useState(0);
const [SupplierData, setSupplierData] = useState([]);
console.log(form?.getFieldsValue(), 'SupplierData', SupplierData);
console.log(form?.getFieldsValue(), 'productTableData', productTableData);
const [showPreviewModal, setShowPreviewModal] = useState(false);
const [processedImages, setProcessedImages] = useState(new Set());
const [uploadMoreVisible, setUploadMoreVisible] = useState(false);
@ -716,8 +722,14 @@ const InvoiceImageExtractorModal = ({
setProcessingStep('Processing image…');
try {
const data = {
file: fileToProcess,
AppId,
CompId,
BranchId
}
const response = await dispatch(
getInvoiceImageData(fileToProcess)
getInvoiceImageData(data)
).unwrap();
if (response?.data?.length > 0 && response?.statusCode === 1) {
console.log('OCR API Response:', response);
@ -762,19 +774,38 @@ const InvoiceImageExtractorModal = ({
if (data.products?.length) {
const mergedProducts = [...productTableData];
data.products.forEach((p) => {
const exists = mergedProducts.some(
(x) => x.description === p.description && x.rate === p.rate
);
if (!exists) mergedProducts.push(p);
});
setProductTableData(
mergedProducts?.map((item, index) => ({
// Sort Y first, then N
const sortedData = [...mergedProducts]
.sort((a, b) => {
if (a?.type === b?.type) return 0;
return a?.type === 'N' ? -1 : 1;
})
.map((item, index) => ({
...item,
srNo: index + 1,
image: item?.image ? item?.image : (uploadImgData?.data?.status ? uploadImgData?.data?.image : null),
}))
);
image: item?.image
? item?.image
: uploadImgData?.data?.status
? uploadImgData?.data?.image
: null,
}));
setProductTableData(sortedData);
// setProductTableData(
// mergedProducts?.map((item, index) => ({
// ...item,
// srNo: index + 1,
// image: item?.image ? item?.image : (uploadImgData?.data?.status ? uploadImgData?.data?.image : null),
// }))
// );
SetTotalAmtData(
mergedProducts.reduce(
(sum, item) =>
@ -1012,6 +1043,7 @@ const InvoiceImageExtractorModal = ({
cursor: 'pointer',
color: '#1292ee',
marginBottom: '10px',
fontFamily: 'Poppins'
}}
onClick={() => setIsEditMode(true)}
>
@ -1151,9 +1183,21 @@ const InvoiceImageExtractorModal = ({
<div style={{ margin: '10px 0', fontSize: '14px', color: '#666' }}>
<strong>Note:</strong> Click on a row to edit the data
</div>
<div className="product-legend">
<div className="legend-item">
<span className="legend-box green"></span>
<span>Product exists in list</span>
</div>
<div className="legend-item">
<span className="legend-box red"></span>
<span>Product not found in list</span>
</div>
</div>
<div className="extractor-table" ref={extractedTableRef}>
<Tables
rowClassName="product-extractor-table"
rowClassName={(record) => `product-extractor-table ${record.type === 'N' ? 'not-added' : 'added'}`}
columns={columns}
data={productTableData}
onRow={(record) => ({

View File

@ -628,6 +628,7 @@ const StockForm = ({ formType }) => {
const unmatchedProducts = matchedProducts.filter(
(item) => !item.matchFound
);
if (unmatchedProducts?.length > 0) {
setLoadingText('Mapping unmatched products...');
const response = await dispatch(
@ -704,7 +705,7 @@ const StockForm = ({ formType }) => {
}));
const bulkResponse = await dispatch(
bulkpostdata({ ProdDetails: newProductsData })
bulkpostdata({ UploadType: 'Purchase', ProdDetails: newProductsData })
).unwrap();
if (bulkResponse?.data?.statusCode === 1) {

View File

@ -1,16 +1,9 @@
.cropUploadFieldSmall {
:where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-card-wrapper
.ant-upload-list.ant-upload-list-picture-card
.ant-upload-list-item-container,
:where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-circle-wrapper
.ant-upload-list.ant-upload-list-picture-card
.ant-upload-list-item-container,
:where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-card-wrapper
.ant-upload-list.ant-upload-list-picture-circle
.ant-upload-list-item-container,
:where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-circle-wrapper
.ant-upload-list.ant-upload-list-picture-circle
.ant-upload-list-item-container {
:where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-card-wrapper .ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item-container,
:where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-circle-wrapper .ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item-container,
:where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-card-wrapper .ant-upload-list.ant-upload-list-picture-circle .ant-upload-list-item-container,
:where(.css-dev-only-do-not-override-1v5z42l).ant-upload-wrapper.ant-upload-picture-circle-wrapper .ant-upload-list.ant-upload-list-picture-circle .ant-upload-list-item-container {
width: 65px;
height: 65px;
}
@ -480,6 +473,7 @@
.ant-form-item {
margin-bottom: 0;
}
.ant-input {
padding: 4px 14px 6px 11px !important;
text-decoration: underline;
@ -504,9 +498,110 @@
width: 100%;
display: flex;
justify-content: flex-end;
@media (max-width: 500px) {
position: fixed;
right: 10px;
bottom: 8px;
}
}
.direct-sale-container {
.uom-select {
width: 90px !important;
}
.tax-select {
width: 120px !important;
}
.tax-select,
.uom-select {
.ant-select-selection-placeholder,
.ant-select-selection-item {
padding-inline-end: 0 !important;
padding-top: 0 !important;
margin-top: 0 !important;
}
}
.ant-select-arrow {
top: 60% !important;
}
.ant-input {
padding: 6px 14px 6px 11px !important;
}
.ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
height: 36.5px !important;
}
}
.directsaleBTN {
.ant-btn {
width: max-content !important;
height: 36px !important;
z-index: 0;
}
.ant-btn:disabled,
.ant-btn[disabled] {
background-color: #f5f5f5;
color: rgba(0, 0, 0, 0.25);
}
.ant-btn:not(:disabled) {
background-color: #10b981;
color: #fff;
font-size: 14px;
height: 36px !important;
border-radius: 6px;
font-family: "Poppins";
&:hover {
background: #ffffff !important;
background-color: #ffffff !important;
color: #10b981;
border-color: #10b981;
}
}
.save-btn {
background-color: #0088ff !important;
&:hover {
color: #0088ff !important;
border-color: #0088ff !important;
}
}
.ant-btn:not(:disabled):nth-child(2) {
background-color: #1292ee;
color: #fff;
font-size: 14px;
height: 36px !important;
border-radius: 6px;
}
}
.DirectSaleTable {
height: 70vh;
overflow: auto;
scrollbar-width: thin;
}
.requiredIcons {
width: 12px;
border-radius: 6px;
height: 12px;
background-color: #fff;
padding: 2px;
}

View File

@ -133,6 +133,11 @@
.extractor-table {
overflow: auto;
scrollbar-width: thin;
}
.extractor-table-red .ant-table-thead {
background-color: #ef4444;
}
.product-extractor-table {
@ -160,6 +165,48 @@
text-align: center;
}
}
&.not-added {
background-color: #ff4d4f;
&:hover {
background-color: #ff4d4f !important;
}
}
&.added {
background-color: #52c41a;
}
}
.product-legend {
display: flex;
align-items: center;
gap: 10px;
font-family: 'Poppins';
margin-bottom: 10px;
.legend-item {
display: flex;
align-items: center;
gap: 10px;
}
.legend-box {
width: 10px;
height: 10px;
border-radius: 2px;
&.green {
background-color: #52c41a;
}
&.red {
background-color: #ff4d4f;
}
}
}
// .supplierInfo .ant-select-selector