upload spreadsheet of opening inventory
This commit is contained in:
parent
c478471124
commit
5f436716e9
@ -167,6 +167,7 @@ import SingleSales from "./views/Sales/SingleSale";
|
|||||||
import MobileApp from "./views/configuration/MobileApp";
|
import MobileApp from "./views/configuration/MobileApp";
|
||||||
import PDOpeningInventory from "./views/OpeningInventory/PDOpeningInventory";
|
import PDOpeningInventory from "./views/OpeningInventory/PDOpeningInventory";
|
||||||
import DistributorOpeningInventory from "./views/PrincipalDistributors/OpeningInventory";
|
import DistributorOpeningInventory from "./views/PrincipalDistributors/OpeningInventory";
|
||||||
|
import UploadOpeningInventory from "./views/PrincipalDistributors/UploadOpeningInventory";
|
||||||
const routes = [
|
const routes = [
|
||||||
//dashboard
|
//dashboard
|
||||||
|
|
||||||
@ -425,6 +426,12 @@ const routes = [
|
|||||||
element: DistributorOpeningInventory,
|
element: DistributorOpeningInventory,
|
||||||
navName: "Distributor",
|
navName: "Distributor",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/:distributortype/opening-inventory/upload/:id",
|
||||||
|
name: " Distributor Opening Inventory Upload",
|
||||||
|
element: UploadOpeningInventory,
|
||||||
|
navName: "Distributor",
|
||||||
|
},
|
||||||
//----------------------- End Product Management Routes------------------------------------------------
|
//----------------------- End Product Management Routes------------------------------------------------
|
||||||
|
|
||||||
//Departure
|
//Departure
|
||||||
|
@ -15,6 +15,7 @@ const DistributorOpeningInventory = () => {
|
|||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [productsData, setProductsData] = useState([]);
|
const [productsData, setProductsData] = useState([]);
|
||||||
|
const [allProductsData, setAllProductsData] = useState([]);
|
||||||
const [categories, setCategories] = useState([]);
|
const [categories, setCategories] = useState([]);
|
||||||
const [brands, setBrands] = useState([]);
|
const [brands, setBrands] = useState([]);
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
@ -77,8 +78,22 @@ const DistributorOpeningInventory = () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
// console.log(response.data.products);
|
||||||
setProductsData(response.data?.products || []);
|
setProductsData(response.data?.products || []);
|
||||||
setTotalData(response.data?.totalProducts || 0);
|
setTotalData(response.data?.totalProducts || 0);
|
||||||
|
// Merge new products with existing ones in allProductsData
|
||||||
|
setAllProductsData((prev) => {
|
||||||
|
const updatedList = [...prev];
|
||||||
|
response.data?.products?.forEach((product) => {
|
||||||
|
const index = updatedList.findIndex((p) => p._id === product._id);
|
||||||
|
if (index > -1) {
|
||||||
|
updatedList[index] = product; // Update existing product
|
||||||
|
} else {
|
||||||
|
updatedList.push(product); // Add new product
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return updatedList;
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
swal({
|
swal({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
@ -157,19 +172,30 @@ const DistributorOpeningInventory = () => {
|
|||||||
...prevStocks,
|
...prevStocks,
|
||||||
[sku]: newStock, // Update stock for specific product (identified by SKU)
|
[sku]: newStock, // Update stock for specific product (identified by SKU)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Update the stock directly in allProductsData
|
||||||
|
setAllProductsData((prev) =>
|
||||||
|
prev.map((product) =>
|
||||||
|
product.SKU === sku ? { ...product, stock: newStock } : product
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmitStocks = async () => {
|
const handleSubmitStocks = async () => {
|
||||||
try {
|
try {
|
||||||
const updatedProducts = productsData.map((product) => ({
|
const updatedProducts = allProductsData.map((product) => ({
|
||||||
...product,
|
_id: product._id,
|
||||||
stock: updatedStocks[product.SKU] || product.stock,
|
SKU: product.SKU,
|
||||||
|
name: product.name,
|
||||||
|
openingInventory: updatedStocks[product.SKU] || product.stock,
|
||||||
}));
|
}));
|
||||||
await axios.post(
|
// console.log(updatedProducts);
|
||||||
|
// console.log(id);
|
||||||
|
await axios.put(
|
||||||
distributortype === "principaldistributor"
|
distributortype === "principaldistributor"
|
||||||
? `/api/pd/updateStocks/${id}`
|
? `/api/pd/stock-update`
|
||||||
: `/api/rd/updateStocks/${id}`,
|
: `/api/rd/stock-update`,
|
||||||
{ updatedProducts },
|
{ products: updatedProducts, userId: id },
|
||||||
{ headers: { Authorization: `Bearer ${token}` } }
|
{ headers: { Authorization: `Bearer ${token}` } }
|
||||||
);
|
);
|
||||||
swal("Success", "Stocks updated successfully!", "success");
|
swal("Success", "Stocks updated successfully!", "success");
|
||||||
@ -215,7 +241,23 @@ const DistributorOpeningInventory = () => {
|
|||||||
<strong>Email:</strong> {user?.email}
|
<strong>Email:</strong> {user?.email}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="page-title-right mr-2">
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className="font-bold capitalize"
|
||||||
|
onClick={() =>
|
||||||
|
navigate(
|
||||||
|
`/principaldistributor/opening-inventory/upload/${id}`,
|
||||||
|
{
|
||||||
|
replace: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Upload Spreadsheet
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
{/* Back Button on the right */}
|
{/* Back Button on the right */}
|
||||||
<div className="page-title-right">
|
<div className="page-title-right">
|
||||||
<Button
|
<Button
|
||||||
@ -236,7 +278,7 @@ const DistributorOpeningInventory = () => {
|
|||||||
<div className="row mt-2 mb-1">
|
<div className="row mt-2 mb-1">
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<div style={{ fontSize: "22px" }} className="fw-bold">
|
<div style={{ fontSize: "22px" }} className="fw-bold">
|
||||||
Product Stocks
|
Product Opening Inventory
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -312,9 +354,23 @@ const DistributorOpeningInventory = () => {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-lg-2">
|
||||||
|
<Button
|
||||||
|
className="mt-4"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={handleSubmitStocks}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-responsive table-shoot mt-3">
|
<div
|
||||||
|
className="table-responsive table-shoot mt-3"
|
||||||
|
style={{ overflowX: "auto" }}
|
||||||
|
>
|
||||||
<table
|
<table
|
||||||
className="table table-centered table-nowrap"
|
className="table table-centered table-nowrap"
|
||||||
style={{ border: "1px solid" }}
|
style={{ border: "1px solid" }}
|
||||||
|
222
src/views/PrincipalDistributors/UploadOpeningInventory.js
Normal file
222
src/views/PrincipalDistributors/UploadOpeningInventory.js
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import swal from "sweetalert";
|
||||||
|
import { isAutheticated } from "src/auth";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
const UploadOpeningInventory = () => {
|
||||||
|
const [file, setFile] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [errors, setErrors] = useState([]);
|
||||||
|
const [newlyCreated, setNewlyCreated] = useState([]);
|
||||||
|
const [updatedInventories, setupdatedInventories] = useState([]);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const token = isAutheticated();
|
||||||
|
const { id, distributortype } = useParams();
|
||||||
|
const handleFileChange = (e) => {
|
||||||
|
const selectedFile = e.target.files[0];
|
||||||
|
if (
|
||||||
|
selectedFile &&
|
||||||
|
selectedFile.type ===
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
} else {
|
||||||
|
swal("Error", "Please upload a valid Excel file", "error");
|
||||||
|
setFile(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!file) {
|
||||||
|
toast.error("Please select a file to upload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// console.log(file);
|
||||||
|
// console.log(token);
|
||||||
|
setLoading(true);
|
||||||
|
setErrors([]);
|
||||||
|
setNewlyCreated([]);
|
||||||
|
setupdatedInventories([]);
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
|
||||||
|
const { data } = await axios.post(
|
||||||
|
`/api/openinginventories/upload/${id}`,
|
||||||
|
formData,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// console.log(data);
|
||||||
|
if (data?.errors && data?.errors?.length > 0) {
|
||||||
|
setErrors(data.errors);
|
||||||
|
}
|
||||||
|
if (data?.newlyCreated && data?.newlyCreated?.length > 0) {
|
||||||
|
setNewlyCreated(data?.newlyCreated);
|
||||||
|
}
|
||||||
|
if (data?.updatedOpeningInventories && data?.updatedOpeningInventories?.length > 0) {
|
||||||
|
setupdatedInventories(data?.updatedOpeningInventories);
|
||||||
|
// console.log(data.updatedOpeningInventories);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect or display success message
|
||||||
|
if (data?.errors && data?.errors.length > 0) {
|
||||||
|
setErrors(data?.errors);
|
||||||
|
swal({
|
||||||
|
title: "SpreadSheet Upload Successful",
|
||||||
|
text: "A few Products have errors. Please fix them and upload again.",
|
||||||
|
icon: "warning",
|
||||||
|
button: "OK",
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
data?.newlyCreated.length > 0 ||
|
||||||
|
data?.updatedInventories.length > 0
|
||||||
|
) {
|
||||||
|
swal({
|
||||||
|
title: "SpreadSheet Upload Successful",
|
||||||
|
text: "Product Opening Inventory uploaded successfully.",
|
||||||
|
icon: "success",
|
||||||
|
buttons: "OK",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast.success("File processed successfully with no new entries.");
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
setFile(null); // Clear the file input
|
||||||
|
document.querySelector('input[type="file"]').value = "";
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Upload error:", error);
|
||||||
|
swal(
|
||||||
|
"Error",
|
||||||
|
`Failed to upload Opening Inventory Spreedsheet: ${
|
||||||
|
error.response?.data?.message || "An unexpected error occurred"
|
||||||
|
}`,
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
navigate(
|
||||||
|
distributortype === "principaldistributor"
|
||||||
|
? `/principaldistributor/opening-inventory/${id}`
|
||||||
|
: `/retaildistributor/opening-inventory/${id}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="container mt-4">
|
||||||
|
<div className="mb-6">
|
||||||
|
<button onClick={handleCancel} className="btn btn-secondary">
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h5 className="mb-6 mt-4">Add Multiple Products Opening inventory</h5>
|
||||||
|
<div className="my-3">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-lg-9">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
name="file"
|
||||||
|
className="form-control"
|
||||||
|
accept=".xlsx"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-lg-3">
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? "Uploading..." : "Upload"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="pt-1 pl-2 text-secondary">Upload only .xlsx files*</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{errors.length > 0 && (
|
||||||
|
<div className="my-4">
|
||||||
|
<h6>Finding errors while adding the Opening Inventory.</h6>
|
||||||
|
<table className="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>SKU</th>
|
||||||
|
<th>Product Name</th>
|
||||||
|
<th>Opening Inventory</th>
|
||||||
|
<th>Message</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{errors.map((error, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>{error.SKU || "N/A"}</td>
|
||||||
|
<td>{error.productName || "N/A"}</td>
|
||||||
|
<td>{error.openingInventory || "N/A"}</td>
|
||||||
|
<td>{error.message}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{updatedInventories.length > 0 && (
|
||||||
|
<div className="my-4">
|
||||||
|
<h6>Updated Products in the Opening Inventory List</h6>
|
||||||
|
<table className="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>SKU</th>
|
||||||
|
<th>Product Name</th>
|
||||||
|
<th>Opening Inventory</th>
|
||||||
|
<th>Message</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{updatedInventories.map((distributor, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>{distributor.SKU || "N/A"}</td>
|
||||||
|
<td>{distributor.productName || "N/A"}</td>
|
||||||
|
<td>{distributor.openingInventory || "N/A"}</td>
|
||||||
|
<td>{distributor.updatedFields}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{newlyCreated.length > 0 && (
|
||||||
|
<div className="my-4">
|
||||||
|
<h6>Newly Added Products in Opening Inventory:</h6>
|
||||||
|
<table className="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>SKU</th>
|
||||||
|
<th>Product Name</th>
|
||||||
|
<th>Opening Inventory</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{newlyCreated.map((distributor, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>{distributor.SKU || "N/A"}</td>
|
||||||
|
<td>{distributor.productName || "N/A"}</td>
|
||||||
|
<td>{distributor.openingInventory }</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UploadOpeningInventory;
|
@ -205,13 +205,17 @@ const principalDistributor = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-responsive table-shoot mt-3">
|
<div
|
||||||
|
className="table-responsive table-shoot mt-3"
|
||||||
|
style={{ overflowX: "auto" }}
|
||||||
|
>
|
||||||
<table
|
<table
|
||||||
className="table table-centered table-nowrap"
|
className="table table-centered table-nowrap"
|
||||||
style={{
|
style={{
|
||||||
border: "1px solid",
|
border: "1px solid",
|
||||||
tableLayout: "fixed",
|
tableLayout: "fixed", // Keeps columns fixed width
|
||||||
width: "100%",
|
width: "100%", // Ensures table takes full width
|
||||||
|
minWidth: "1000px", // Prevents the table from shrinking too much
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<thead
|
<thead
|
||||||
|
Loading…
Reference in New Issue
Block a user