415 lines
16 KiB
JavaScript
415 lines
16 KiB
JavaScript
"use strict";
|
|
|
|
let allProducts = [];
|
|
|
|
// Asset path helper
|
|
function assetPath(path) {
|
|
let baseUrl = $("#asset_base_url").val() || "";
|
|
return baseUrl.replace(/\/$/, "") + "/" + path.replace(/^\/+/, "");
|
|
}
|
|
|
|
// renders product dropdown list
|
|
function populateProducts(products) {
|
|
const $dropdownList = $("#dropdownList");
|
|
$dropdownList.empty();
|
|
|
|
if (products.length === 0) {
|
|
$dropdownList.append(
|
|
'<div class="product-option-item">No products available</div>'
|
|
);
|
|
return;
|
|
}
|
|
|
|
const cartRoute = $("#cart-store-url").val();
|
|
|
|
$.each(products, function (index, product) {
|
|
const imageUrl = assetPath(
|
|
product.productPicture ?? "assets/images/products/box.svg"
|
|
);
|
|
let html = "";
|
|
|
|
// if multiple stocks
|
|
if (product.stocks && product.stocks.length > 1) {
|
|
html += `<div class="product-option-item single-product"
|
|
data-product_id="${product.id}"
|
|
data-route="${cartRoute}"
|
|
data-product_name="${product.productName}"
|
|
data-product_code="${product.productCode}"
|
|
data-product_unit_id="${product.unit_id}"
|
|
data-product_unit_name="${product.unit?.unitName ?? ""}"
|
|
data-product_image="${product.productPicture}">
|
|
<div class="product-left">
|
|
<img src="${imageUrl}" alt="">
|
|
<div class="product-text">
|
|
<div class="d-flex align-items-center justify-content-between w-100">
|
|
<div class="product-title">${
|
|
product.productName
|
|
}</div>
|
|
<p>Code : ${product.productCode}</p>
|
|
</div>`;
|
|
|
|
product.stocks.forEach((stock) => {
|
|
html += `<div class="d-flex align-items-center justify-content-between w-100 multi-items add-batch-item"
|
|
data-product_stock_id="${stock.id}"
|
|
data-product_id="${product.id}"
|
|
data-product_expire_date="${stock.expire_date ?? ""}"
|
|
data-product_name="${product.productName}"
|
|
data-product_code="${product.productCode}"
|
|
data-default_price="${stock.productSalePrice}"
|
|
data-product_unit_id="${product.unit_id}"
|
|
data-product_unit_name="${product.unit?.unitName ?? ""}"
|
|
data-purchase_price="${stock.productPurchasePrice}"
|
|
data-product_image="${product.productPicture}"
|
|
data-route="${cartRoute}"
|
|
data-batch_no="${stock.batch_no ?? ''}"
|
|
>
|
|
|
|
<div class="product-des">
|
|
Batch: ${stock.batch_no ?? "N/A"} ${
|
|
product.color ? ", Color: " + product.color : ""
|
|
}
|
|
<span class="product-in-stock">In Stock: ${
|
|
stock.productStock
|
|
}</span>
|
|
</div>
|
|
<div class="product-price product_price">${currencyFormat(
|
|
stock.productSalePrice
|
|
)}</div>
|
|
</div>`;
|
|
});
|
|
|
|
html += `</div></div></div>`;
|
|
} else {
|
|
const singleStock =
|
|
Array.isArray(product.stocks) && product.stocks.length > 0
|
|
? product.stocks[0]
|
|
: {};
|
|
|
|
html += `<div class="product-option-item single-product ${
|
|
product.id
|
|
} add-batch-item"
|
|
data-product_stock_id="${singleStock.id ?? ""}"
|
|
data-product_id="${product.id}"
|
|
data-default_price="${singleStock.productSalePrice ?? 0}"
|
|
data-product_name="${product.productName}"
|
|
data-product_code="${product.productCode}"
|
|
data-product_unit_id="${product.unit_id}"
|
|
data-product_unit_name="${product.unit?.unitName ?? ""}"
|
|
data-purchase_price="${singleStock.productPurchasePrice ?? 0}"
|
|
data-product_image="${product.productPicture}"
|
|
data-product_expire_date="${singleStock.expire_date ?? ""}"
|
|
data-route="${cartRoute}"
|
|
data-batch_no="${singleStock.batch_no ?? ''}">
|
|
|
|
<div class="product-left">
|
|
<img src="${imageUrl}" alt="">
|
|
<div class="product-text">
|
|
<div class="d-flex align-items-center justify-content-between w-100">
|
|
<div class="product-title">${
|
|
product.productName
|
|
}</div>
|
|
<p>Code : ${product.productCode}</p>
|
|
</div>
|
|
<div class="d-flex align-items-center justify-content-between w-100">
|
|
<div class="product-des">
|
|
Batch: ${singleStock.batch_no ?? "N/A"} ${
|
|
product.color ? ", Color: " + product.color : ""
|
|
}
|
|
<span class="product-in-stock">In Stock: ${
|
|
Array.isArray(product.stocks)
|
|
? product.stocks.reduce(
|
|
(sum, stock) =>
|
|
sum + stock.productStock,
|
|
0
|
|
)
|
|
: 0
|
|
}</span>
|
|
</div>
|
|
<div class="product-price product_price">${currencyFormat(
|
|
singleStock.productSalePrice ?? 0
|
|
)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
$dropdownList.append(html);
|
|
});
|
|
}
|
|
|
|
// load all products initially
|
|
const allProductsUrl = $("#all-products").val();
|
|
if (allProductsUrl) {
|
|
$.get(allProductsUrl, function (products) {
|
|
allProducts = products; // store globally
|
|
populateProducts(allProducts);
|
|
});
|
|
}
|
|
|
|
// Filter products based on branch & warehouse
|
|
function filterProducts() {
|
|
const branchId = $("#from_branch").val();
|
|
const warehouseId = $("#from_warehouse").val();
|
|
|
|
const filtered = allProducts
|
|
.map((product) => {
|
|
if (!product.stocks || product.stocks.length === 0) return null;
|
|
|
|
// filter stocks by selected branch & warehouse
|
|
const filteredStocks = product.stocks.filter((stock) => {
|
|
const branchMatch = !branchId || stock.branch_id == branchId;
|
|
const warehouseMatch = !warehouseId || stock.warehouse_id == warehouseId;
|
|
return branchMatch && warehouseMatch && stock.productStock > 0;
|
|
});
|
|
|
|
if (filteredStocks.length === 0) return null;
|
|
|
|
return {
|
|
...product, // copy product data, keep only filtered stocks
|
|
stocks: filteredStocks,
|
|
};
|
|
})
|
|
.filter(Boolean); // remove nulls
|
|
|
|
populateProducts(filtered);
|
|
}
|
|
|
|
// toggle product dropdown visibility
|
|
const $dropdown = $("#dropdownList");
|
|
const $searchContainer = $("#searchContainer");
|
|
const $productSearch = $("#productSearch");
|
|
const $arrow = $("#arrow");
|
|
|
|
$("#productDropdown .product-selected").on("click", function (e) {
|
|
e.stopPropagation();
|
|
$dropdown.toggle();
|
|
$searchContainer.toggleClass("hidden");
|
|
$arrow.toggleClass("product-rotate");
|
|
});
|
|
|
|
// Close dropdown if clicked outside
|
|
$(document).on("click", function (e) {
|
|
if (!$(e.target).closest("#productDropdown").length) {
|
|
$dropdown.hide();
|
|
$searchContainer.addClass("hidden");
|
|
$arrow.removeClass("product-rotate");
|
|
}
|
|
});
|
|
|
|
// product search also respects filter
|
|
$productSearch.on("keyup", function () {
|
|
const searchTerm = $(this).val().toLowerCase();
|
|
$("#dropdownList .product-option-item").each(function () {
|
|
const name = String($(this).data("product_name") || "").toLowerCase();
|
|
const code = String($(this).data("product_code") || "").toLowerCase();
|
|
$(this).toggle(name.includes(searchTerm) || code.includes(searchTerm));
|
|
});
|
|
});
|
|
|
|
// Add to cart on batch click
|
|
$("#dropdownList").on("click", ".add-batch-item", function (e) {
|
|
e.stopPropagation();
|
|
|
|
const $item = $(this);
|
|
$dropdown.hide();
|
|
$searchContainer.addClass("hidden");
|
|
$arrow.removeClass("product-rotate");
|
|
|
|
let stockId = $item.data("product_stock_id");
|
|
let productId = $item.data("product_id");
|
|
|
|
let name = $item.data("product_name");
|
|
let code = $item.data("product_code");
|
|
let price = parseFloat($item.data("default_price") || 0);
|
|
let batchNo = $item.data("batch_no") || "";
|
|
let image = $item.data("product_image") || assetPath("assets/images/products/box.svg");
|
|
|
|
// If row exists increment qty
|
|
let existingRow = $("#product-row-" + stockId);
|
|
if (existingRow.length) {
|
|
let qtyInput = existingRow.find(".dynamic-qty");
|
|
qtyInput.val((parseInt(qtyInput.val()) || 1) + 1).trigger("input");
|
|
return;
|
|
}
|
|
|
|
let rowHtml = `
|
|
<tr id="product-row-${stockId}">
|
|
<td><img src="${assetPath(image)}" width="40"></td>
|
|
<td>${name}</td>
|
|
<td>${code}</td>
|
|
<td>${batchNo}</td>
|
|
<td class="text-start">
|
|
<div class="d-flex align-items-center justify-content-center">
|
|
<button type="button" class="incre-decre subtract-btn btn btn-sm btn-outline-secondary"><i class="fas fa-minus"></i></button>
|
|
<input type="number" name="products[${stockId}][quantity]" class="custom-number-input dynamic-qty form-control form-control-sm mx-1 text-center" value="1" min="1" step="1" style="width: 60px;">
|
|
<button type="button" class="incre-decre adding-btn btn btn-sm btn-outline-secondary"><i class="fas fa-plus"></i></button>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<input type="number" name="products[${stockId}][unit_price]" class="form-control unit-price text-center" value="${price}" min="0">
|
|
</td>
|
|
<td><input type="number" name="products[${stockId}][tax]" class="form-control tax text-center" value="0" min="0"></td>
|
|
<td><input type="number" name="products[${stockId}][discount]" class="form-control discount text-center" value="0" min="0"></td>
|
|
<td class="sub-total text-center">${price.toFixed(2)}</td>
|
|
<td>
|
|
<button type="button" class="x-btn remove-btn" data-id="${stockId}">
|
|
<svg width="25" height="24" viewBox="0 0 25 24" fill="none">
|
|
<path d="M18.5 6L6.5 18" stroke="#E13F3D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<path d="M6.5 6L18.5 18" stroke="#E13F3D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</button>
|
|
</td>
|
|
<input type="hidden" name="products[${stockId}][product_id]" value="${productId}">
|
|
</tr>
|
|
`;
|
|
$("#product-list").append(rowHtml);
|
|
updateTotals();
|
|
});
|
|
|
|
// Cart qty, price, discount, tax change
|
|
$("#product-list").on(
|
|
"input",
|
|
".dynamic-qty, .unit-price, .discount, .tax",
|
|
function () {
|
|
let $row = $(this).closest("tr");
|
|
let qty = parseFloat($row.find(".dynamic-qty").val()) || 0;
|
|
let price = parseFloat($row.find(".unit-price").val()) || 0;
|
|
let discount = parseFloat($row.find(".discount").val()) || 0;
|
|
let tax = parseFloat($row.find(".tax").val()) || 0;
|
|
|
|
let subtotal = qty * price + tax - discount;
|
|
$row.find(".sub-total").text(subtotal.toFixed(2));
|
|
updateTotals();
|
|
}
|
|
);
|
|
|
|
// increment / decrement buttons
|
|
$("#product-list").on("click", ".adding-btn", function () {
|
|
let $row = $(this).closest("tr");
|
|
let $qty = $row.find(".dynamic-qty");
|
|
$qty.val((parseInt($qty.val()) || 0) + 1).trigger("input");
|
|
});
|
|
$("#product-list").on("click", ".subtract-btn", function () {
|
|
let $row = $(this).closest("tr");
|
|
let $qty = $row.find(".dynamic-qty");
|
|
let current = parseInt($qty.val()) || 1;
|
|
if (current > 1) $qty.val(current - 1).trigger("input");
|
|
});
|
|
|
|
// remove row
|
|
$("#product-list").on("click", ".remove-btn", function () {
|
|
$(this).closest("tr").remove();
|
|
updateTotals();
|
|
});
|
|
|
|
// update totals function
|
|
function updateTotals() {
|
|
let subTotal = 0,
|
|
totalDiscount = 0,
|
|
totalTax = 0,
|
|
grandTotal = 0;
|
|
|
|
$("#product-list tr").each(function () {
|
|
let qty = parseFloat($(this).find(".dynamic-qty").val()) || 0;
|
|
let price = parseFloat($(this).find(".unit-price").val()) || 0;
|
|
let discount = parseFloat($(this).find(".discount").val()) || 0;
|
|
let tax = parseFloat($(this).find(".tax").val()) || 0;
|
|
|
|
let lineBase = qty * price;
|
|
let lineWithTax = lineBase + tax;
|
|
let lineSubtotal = lineWithTax - discount;
|
|
|
|
$(this).find(".sub-total").text(lineSubtotal.toFixed(2));
|
|
|
|
subTotal += lineBase;
|
|
totalTax += tax;
|
|
totalDiscount += discount;
|
|
grandTotal += lineSubtotal;
|
|
});
|
|
|
|
let shipping = parseFloat($("#shipping_amount").val()) || 0;
|
|
grandTotal += shipping;
|
|
|
|
$("#total_amount").text(subTotal.toFixed(2));
|
|
$("#tax_amount").text(totalTax.toFixed(2));
|
|
$("#discount_amount").text(totalDiscount.toFixed(2));
|
|
$("#grand_total_amount").text(grandTotal.toFixed(2));
|
|
}
|
|
|
|
// Trigger totals update on shipping change
|
|
$("#shipping_amount").on("input", function () {
|
|
updateTotals();
|
|
});
|
|
|
|
const allWarehousesUrl = $("#branch_wise_warehouses").val();
|
|
|
|
function loadWarehouses(branchId, warehouseSelect) {
|
|
if (!allWarehousesUrl) return;
|
|
|
|
$.ajax({
|
|
url: allWarehousesUrl,
|
|
type: "GET",
|
|
data: { branch_id: branchId },
|
|
success: function (warehouses) {
|
|
let options = '<option value="">Select one</option>';
|
|
|
|
if (warehouses.length > 0) {
|
|
$.each(warehouses, function (i, wh) {
|
|
options += `<option value="${wh.id}">${wh.name}</option>`;
|
|
});
|
|
} else {
|
|
options += `<option value="">No warehouse available</option>`;
|
|
}
|
|
|
|
$(warehouseSelect).html(options);
|
|
},
|
|
error: function () {
|
|
$(warehouseSelect).html('<option value="">Error loading warehouses</option>');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Load all warehouses initially
|
|
loadWarehouses("", "#from_warehouse");
|
|
loadWarehouses("", "#to_warehouse");
|
|
|
|
// When "from_branch" changes
|
|
$("#from_branch").on("change", function () {
|
|
let branchId = $(this).val();
|
|
|
|
// Clear cart and update totals
|
|
$("#product-list").empty();
|
|
updateTotals();
|
|
|
|
// Reload "from_warehouse" options based on selected branch
|
|
loadWarehouses(branchId, "#from_warehouse");
|
|
|
|
// Filter products based on new branch & warehouse
|
|
filterProducts();
|
|
});
|
|
|
|
// When "to_branch" changes
|
|
$("#to_branch").on("change", function () {
|
|
let branchId = $(this).val();
|
|
let hasActiveBranch = $("#hasActiveBranch").val() == "1";
|
|
|
|
// Only reload "to_warehouse" if no active branch
|
|
if (!hasActiveBranch) {
|
|
loadWarehouses(branchId, "#to_warehouse");
|
|
}
|
|
});
|
|
|
|
// When "from_warehouse" changes
|
|
$("#from_warehouse").on("change", function () {
|
|
// Clear cart and update totals
|
|
$("#product-list").empty();
|
|
updateTotals();
|
|
|
|
// Filter products based on new warehouse
|
|
filterProducts();
|
|
});
|
|
|
|
|
|
|