"use strict"; // product img upload code start ----------------> $(document).ready(function () { // Choices.js initializer function initChoices($root) { $root.find(".tagSelect").each(function () { new Choices(this, { removeItemButton: true, placeholder: true, placeholderValue: "Select variations", }); }); } const $container = $("#productTypeContainer"); // When product type changes $("#product-type").on("change", function () { loadComponent($(this).val()); }); loadComponent($("#product-type").val()); // Main function function loadComponent(type) { // Remove existing component from DOM $container.empty(); // Choose template let templateHtml; if (type === "combo") { $(".serial-code-checkbox").addClass("d-none"); $(".low-stock-input").addClass("d-none"); templateHtml = $("#comboTemplate").html(); loadAllProducts(); } else if (type === "single") { $(".serial-code-checkbox").removeClass("d-none"); $(".low-stock-input").removeClass("d-none"); templateHtml = $("#singleTemplate").html(); } else { $(".serial-code-checkbox").removeClass("d-none"); $(".low-stock-input").removeClass("d-none"); templateHtml = $("#variationTemplate").html(); setTimeout(() => { resetVariantRows(false); }, 100); } // Append to container $container.append($(templateHtml)); if (!$("#previous-stocks").length || $("#previous-stocks").val() === "[]") { $('.batch_no').each(function (index) { if (!$(this).val()) { $(this).val($('#productCode').val() + '-' + (index + 1)); } }); } // Re-initialize Choices.js for newly inserted selects initChoices($container); return; } $(document).on("change", ".tagSelect[name='variation_ids[]']", function () { resetVariantRows(true); }); function resetVariantRows(regenerateRows) { const selectedVariationIds = $(".variation_ids").val() || []; const variationsData = JSON.parse($("#variations-data").val() || "[]"); const selectedValuesMap = JSON.parse( $("#selected-variation-values").val() || "{}" ); const $variationContainer = $(".variation-values-container"); $variationContainer.empty(); // clear old variation selects selectedVariationIds.forEach(function (variationId) { const variation = variationsData.find((v) => v.id == variationId); if (!variation) return; const selectedValues = selectedValuesMap[variation.name] || []; const selectHtml = `
`; $variationContainer.append(selectHtml); }); // Initialize Choices for newly added selects initChoices($variationContainer); if (regenerateRows) { generateVariantRows(); } } // When variation values change (Size, Color, etc.) $(document).on("change", ".variation-value", function () { generateVariantRows(); }); let prevVariantName = ""; // Function to generate all variant combinations and render table rows function generateVariantRows() { const $tbody = $("#product-data"); $tbody.empty(); const canSeePrice = $("#canSeePrice").val() == "1"; const permissions = JSON.parse($("#permissions-data").val()); const previousStocks = JSON.parse($("#previous-stocks").val() || "[]"); const warehouses = JSON.parse($("#warehouses-data").val()); // Gather all selected variation values const variationSets = []; $(".variation-value").each(function () { const name = $(this).data("name"); const values = $(this).val() || []; if (values.length > 0) { variationSets.push({ name, values }); } }); if (variationSets.length === 0) { $tbody.html(`Please select a variation.`); return; } // Generate all combinations const allCombinations = getCombinations(variationSets.map((v) => v.values)); const vatType = $("#vat_type").val(); const vatRate = $("#vatRate").val(); const productCode = $("#productCode").val(); const hasSerials = $("#has-serial-code-addon").val(); let index = 0; allCombinations.forEach((combo) => { const variantName = combo.join(" - "); const batch_no = productCode ? productCode + "-" + (index + 1) : ""; const existingStockRows = previousStocks.filter(stock => stock.variant_name == variantName); const rowsToGenerate = existingStockRows.length > 0 ? existingStockRows : [null]; const variationJson = variationSets.map((set, i) => ({ [set.name]: combo[i] || "" })); rowsToGenerate.forEach((stockItem) => { const rowId = generateRowId(); appendStockRow({ rowId, hasSerials, variantName, variationJson, stockItem, batch_no, warehouses, permissions, canSeePrice, vatType, vatRate }); index++; }); prevVariantName = ""; }); } function appendStockRow({ rowId, hasSerials = false, variantName, variationJson, stockItem = null, batch_no = "", warehouses = [], permissions = {}, canSeePrice = false, vatType = "exclusive", vatRate = 0 }) { let newRow = ` `; newRow += `${ prevVariantName === variantName ? '' : '+' }`; newRow += `${ prevVariantName === variantName ? arrowSVG : variantName}`; if (permissions.show_batch_no) { newRow += ``; } if (permissions.show_warehouse) { let warehouseOptions = ''; warehouses.forEach(function (wh) { warehouseOptions += ``; }); newRow += ``; } let needSerial = $(".imei-serial").is(":checked"); if (hasSerials == 1) { newRow += serialIconHtml(needSerial); } if (permissions.show_product_stock) { newRow += ``; } if (canSeePrice) { if (permissions.show_exclusive_price) { const price = stockItem?.productPurchasePrice ?? 0; newRow += ``; } if (permissions.show_inclusive_price) { newRow += ``; } if (permissions.show_profit_percent) { newRow += ``; } } if (permissions.show_product_sale_price) { newRow += ``; } if (permissions.show_product_wholesale_price) { newRow += ``; } if (permissions.show_product_dealer_price) { newRow += ``; } if (permissions.show_mfg_date) { newRow += ``; } if (permissions.show_expire_date) { newRow += ``; } if (prevVariantName == variantName) { newRow += `${deleteSVG}`; } newRow += ``; prevVariantName = variantName; $("#product-data").append($(newRow)); } $(document).on("click", ".add-row-icon", function () { const $currentRow = $(this).closest("tr"); const oldRowID = $currentRow.attr("data-row-id"); const $clonedRow = $currentRow.clone(false, false); // Assign new unique row ID const newRowID = generateRowId(); // Unique value $clonedRow.attr("data-row-id", newRowID); // Update serial button to use new ID $clonedRow.find(".serial-cell-button").attr("data-row-id", newRowID); // Visual adjustment $clonedRow.find(".add-row-icon").text(""); $clonedRow.find(".add-row-icon").removeClass("add-row-icon"); $clonedRow.find(".variant-name").html(arrowSVG); $clonedRow.find(".batch_no").val(''); // Add delete button $clonedRow.append(`${deleteSVG}`); // Update all input names with newRowID $clonedRow.find("input, select, textarea").each(function () { const currentName = $(this).attr("name"); if (currentName) { const updatedName = currentName.replace(oldRowID, newRowID); $(this).attr("name", updatedName); } }); // Insert after parent $currentRow.after($clonedRow); // reGenerateBatchNo(); }); $(document).on("click", ".delete-row", function () { let deletedRow = $(this).closest("tr"); let deletedRowId = deletedRow.data("row-id"); deletedRow.remove(); clearAllSerials(deletedRowId); // reGenerateBatchNo(); }); // function reGenerateBatchNo() { // let productCode = ($('#productCode').val()).toString(); // $('.variation-table tr').each(function (i) { // let batch_no = productCode + '-' + i; // $(this).find('.batch_no').val(batch_no); // }); // } // Helper: generate combinations of variation values function getCombinations(arrays) { if (arrays.length === 0) return []; if (arrays.length === 1) return arrays[0].map((v) => [v]); const result = []; const allCasesOf = (arrays, prefix = []) => { const first = arrays[0]; const rest = arrays.slice(1); for (let value of first) { const newPrefix = [...prefix, value]; if (rest.length === 0) { result.push(newPrefix); } else { allCasesOf(rest, newPrefix); } } }; allCasesOf(arrays); return result; } $(".upload-box").each(function () { if ($(this).find(".preview-wrapper").length > 0) { $(this).find("svg").first().hide(); $(this).find(".upload-text").hide(); } }); }); $(document).on("change", ".handle-image-Upload", function (e) { const input = this; const file = input.files[0]; if (!file) return; const $box = $(input).closest(".upload-box"); $box.find("svg").first().hide(); $box.find(".upload-text").hide(); $box.find(".preview-wrapper").remove(); const previewURL = URL.createObjectURL(file); const previewHTML = `
`; $box.append(previewHTML).addClass("uploaded"); }); $(document).on("click", ".close-icon", function () { const $box = $(this).closest(".upload-box"); $box.find(".preview-wrapper").remove(); $box.find("input[type='file']").val(""); $box.find("svg").first().show(); $box.find(".upload-text").show(); $box.removeClass("uploaded"); }); // product img upload code end----------------> const arrowSVG = ``; const deleteSVG = ``; function generateRowId() { return 'row-' + Date.now() + '-' + Math.floor(Math.random() * 1000); } function serialIconHtml(needSerial) { return ` ` } $(document).on("click", ".add-variant-btn", function (e) { e.preventDefault(); var canSeePrice = $("#canSeePrice").val() == "1"; const permissions = JSON.parse($("#permissions-data").val()); const warehouses = JSON.parse( document.getElementById("warehouses-data").value ); let rowId = generateRowId(); let newRow = ""; if (permissions.show_batch_no) { newRow += ``; } if (permissions.show_warehouse) { let warehouseOptions = ''; warehouses.forEach(function (wh) { warehouseOptions += ``; }); newRow += ` `; } let needSerial = $(".imei-serial").is(":checked"); if ($("#has-serial-code-addon").val() == 1) { newRow += serialIconHtml(needSerial); } if (permissions.show_product_stock) { newRow += ``; } if (canSeePrice) { if (permissions.show_exclusive_price) { newRow += ``; } if (permissions.show_inclusive_price) { newRow += ``; } if (permissions.show_profit_percent) { newRow += ``; } } if (permissions.show_product_sale_price) { newRow += ``; } if (permissions.show_product_wholesale_price) { newRow += ``; } if (permissions.show_product_dealer_price) { newRow += ``; } if (permissions.show_mfg_date) { newRow += ``; } if (permissions.show_expire_date) { newRow += ``; } if (permissions.show_action) { newRow += ` `; } newRow += ""; $("#product-data").append(newRow); }); // Get VAT rate function getVatRate() { return ( parseFloat($("#vat_id").find("option:selected").data("vat_rate")) || 0 ); } // Get profit calculation type (markup/margin) function getProfitOption() { return $("#profit_option").val(); } // Update inclusive_price field based on VAT function updateInclusiveFromExclusive($row) { const vatRate = getVatRate(); const vatType = $("#vat_type").val(); const exclusiveInput = $row.find(".exclusive_price"); const inclusiveInput = $row.find(".inclusive_price"); let exclusive = parseFloat(exclusiveInput.val()) || 0; // inclusive = exclusive + VAT% if (vatType && vatRate) { inclusiveInput.val( (exclusive + (exclusive * vatRate) / 100).toFixed(2) ); } else { inclusiveInput.val(exclusive.toFixed(2)); } } // Calculate MRP from cost and profit function calculateMrpRow($row) { const vatRate = getVatRate(); const vatType = $("#vat_type").val(); const profitOption = getProfitOption(); const costInput = $row.find(".exclusive_price"); const profitInput = $row.find(".profit_percent"); const saleInput = $row.find(".productSalePrice"); let cost = parseFloat(costInput.val()) || 0; let profit = parseFloat(profitInput.val()) || 0; updateInclusiveFromExclusive($row); if (cost > 0) { let basePrice = cost; if (vatType === "inclusive") { basePrice += (cost * vatRate) / 100; } let mrp = 0; if (profitOption === "margin") { mrp = basePrice / (1 - profit / 100); } else { mrp = basePrice + (basePrice * profit) / 100; } saleInput.val(mrp.toFixed(2)); } } // Calculate profit from MRP function calculateProfitFromMrp($row) { const vatRate = getVatRate(); const vatType = $("#vat_type").val(); const profitOption = getProfitOption(); const costInput = $row.find(".exclusive_price"); const profitInput = $row.find(".profit_percent"); const saleInput = $row.find(".productSalePrice"); let cost = parseFloat(costInput.val()) || 0; let mrp = parseFloat(saleInput.val()) || 0; updateInclusiveFromExclusive($row); if (cost > 0 && mrp > 0) { let basePrice = cost; if (vatType === "inclusive") { basePrice += (cost * vatRate) / 100; } let profit = 0; if (profitOption === "margin") { profit = ((mrp - basePrice) / mrp) * 100; } else { profit = ((mrp - basePrice) / basePrice) * 100; } profitInput.val(profit.toFixed(2)); } } function updateExclusiveFromInclusive($row) { const vatRate = getVatRate(); const inclusiveInput = $row.find(".inclusive_price"); const exclusiveInput = $row.find(".exclusive_price"); let inclusive = parseFloat(inclusiveInput.val()) || 0; // Reverse VAT: exclusive = inclusive / (1 + VAT%) let exclusive = inclusive / (1 + vatRate / 100); exclusiveInput.val(exclusive.toFixed(2)); // Recalculate MRP and profit calculateMrpRow($row); } // Bind events for real-time calculation function bindMrpCalculation() { $(document).on( "input change", ".exclusive_price, .profit_percent", function () { const $row = $(this).closest("tr").length ? $(this).closest("tr") : $(this).closest(".row"); calculateMrpRow($row); } ); $(document).on("input change", ".productSalePrice", function () { const $row = $(this).closest("tr").length ? $(this).closest("tr") : $(this).closest(".row"); calculateProfitFromMrp($row); }); $("#vat_id, #vat_type").on("change", function () { $(".exclusive_price").each(function () { const $row = $(this).closest("tr").length ? $(this).closest("tr") : $(this).closest(".row"); calculateMrpRow($row); }); }); // On inclusive price change, update exclusive and MRP after edit $(document).on("blur", ".inclusive_price", function () { const $row = $(this).closest("tr").length ? $(this).closest("tr") : $(this).closest(".row"); updateExclusiveFromInclusive($row); }); } bindMrpCalculation(); /** INVENTORY SALE START **/ // Safely builds asset URLs function assetPath(path) { const baseUrl = $("#asset_base_url").val() || ""; return `${baseUrl.replace(/\/$/, "")}/${(path || "").replace(/^\/+/, "")}`; } // Render product dropdown list with batch-level stock/pricing info function populateProducts(products = []) { const $dropdownList = $("#dropdownList"); $dropdownList.empty(); if (!Array.isArray(products) || products.length === 0) { $dropdownList.html( '
No products available
' ); return; } const html = products .map((product) => { const imageUrl = assetPath( product.productPicture ?? "assets/images/products/box.svg" ); let batchesHtml = ""; if (Array.isArray(product.stocks) && product.stocks.length > 0) { batchesHtml = product.stocks .map((stock) => { const warehouseText = stock.warehouse?.name ? `, Warehouse: ${stock.warehouse.name}` : ""; return `
Batch: ${stock.batch_no ?? "N/A"} ${ product.color ? ", Color: " + product.color : "" } ${warehouseText} In Stock: ${ stock.productStock ?? 0 }
${currencyFormat(stock.productSalePrice)}
`; }) .join(""); } else { batchesHtml = `
No batch info available
`; } return `
${ product.productName }

Code: ${product.productCode}

${batchesHtml}
`; }) .join(""); $dropdownList.html(html); } // Load all products initially function loadAllProducts() { const allProductsUrl = $("#all-products").val(); $.get(allProductsUrl) .done(populateProducts) .fail(() => $("#dropdownList").html( '
Failed to load products
' ) ); } // Delegated event: toggle dropdown (works for dynamic content) $(document).on("click", "#productDropdown .product-selected", function (e) { e.stopPropagation(); const $dropdown = $("#productDropdown").find("#dropdownList"); const $searchContainer = $("#productDropdown").find("#searchContainer"); const $arrow = $("#productDropdown").find("#arrow"); $dropdown.toggle(); $searchContainer.toggleClass("hidden"); $arrow.toggleClass("product-rotate"); }); // Add to cart on batch click $(document).on("click", "#dropdownList .add-batch-item", function (e) { e.stopPropagation(); const $item = $(this); const $selectedText = $("#selectedValue"); const $selectedValueInput = $("#selectedProductValue"); const productName = $item.data("product_name") || "Unknown Product"; $selectedText.text(productName); $selectedValueInput.val($item.data("product_stock_id") || ""); $("#dropdownList").hide(); $("#searchContainer").addClass("hidden"); $("#arrow").removeClass("product-rotate"); addItem($item); }); // Close dropdown and search if clicked outside $(document).on("click", function (e) { if (!$(e.target).closest("#productDropdown").length) { $("#dropdownList").hide(); $("#searchContainer").addClass("hidden"); $("#arrow").removeClass("product-rotate"); } }); // Product search with debounce let searchTimeout; $(document).on("keyup", "#productSearch", function () { clearTimeout(searchTimeout); const searchTerm = $(this).val().toLowerCase(); searchTimeout = setTimeout(() => { $("#dropdownList .single-product").each(function () { const $this = $(this); const name = ($this.data("product_name") || "").toLowerCase(); const code = ($this.data("product_code") || "").toLowerCase(); $this.toggle( name.includes(searchTerm) || code.includes(searchTerm) ); }); }, 150); }); function addItem(element) { let product_id = element.data("product_id"); let product_name = element.data("product_name"); let product_code = element.data("product_code"); let stock_id = element.data("stock_id"); let product_unit_name = element.data("product_unit_name"); let purchase_price = parseFloat(element.data("purchase_price")) || 0; let batch_no = element.data("batch_no") || "N/A"; // Handle null batch let $tableBody = $("#combo-product-rows"); // 🔍 Check if same product & same batch already exists let $existingRow = $tableBody.find( `.combo-row[data-product-id="${product_id}"][data-batch-no="${batch_no}"]` ); if ($existingRow.length) { // 🔁 If exists, increase quantity instead of adding new row let $qtyInput = $existingRow.find(".quantity"); let currentQty = parseFloat($qtyInput.val()) || 0; $qtyInput.val(currentQty + 1); updateComboTotal(); return; } // 🧩 Create new row if not exists let newRow = `
${product_name}

Code: ${product_code}, Batch No: ${batch_no}

${product_unit_name} ${purchase_price.toFixed(2)} `; // Append new row let $totalRow = $tableBody.find(".total-row"); if ($totalRow.length) { $(newRow).insertBefore($totalRow); } else { $tableBody.append(newRow); } updateComboTotal(); } // Update totals function updateComboTotal() { let total = 0; $("#combo-product-rows .combo-row").each(function () { let qty = parseFloat($(this).find(".quantity").val()) || 0; let price = parseFloat($(this).find(".purchase_price").val()) || 0; let rowTotal = qty * price; $(this).find(".sub-total").text(rowTotal.toFixed(2)); total += rowTotal; }); let $tbody = $("#combo-product-rows"); $tbody.find(".total-row").remove(); $tbody.append(`

Net Total Amount:

$${total.toFixed( 2 )}

`); calculateSellAmount(total); } // Update total live $(document).on( "input", ".quantity, .purchase_price, #profit_percent, #vat_type, #vat_id", function () { if ($("#product-type").val() == "combo") { updateComboTotal(); } } ); // Remove row $(document).on("click", ".remove-combo-item", function () { $(this).closest("tr").remove(); updateComboTotal(); }); function calculateSellAmount(total) { let vat_type = $("#vat_type").val(); let vat_percent = parseFloat($("#vat_id").find("option:selected").data("vat_rate")) || 0; let vat_amount = 0; if (vat_type == "inclusive") { vat_amount = (total * vat_percent) / 100; } let total_amount = total + vat_amount; let profit_percent = parseFloat($("#profit_percent").val()) || 0; let profit_amount = (total_amount * profit_percent) / 100; let grand_total = total_amount + profit_amount; $("#productSalePrice").val(grand_total.toFixed(2)); } $("#serial").on("change", function () { if ($(this).is(":checked")) { $(".serial-option").removeClass("d-none"); $(".productStock").attr("readonly", true); } else { $(".serial-option").addClass("d-none"); $(".productStock").attr("readonly", false); } }); /** INVENTORY SALE END **/ // SERIAL CODES START FROM HERE let currentRowId = 0; // When clicking the serial icon on a row $(document).on("click", ".serial-cell-button", function () { $(".serial-input").val(""); $('#serialModal').modal("show"); currentRowId = $(this).closest("tr").data("row-id"); loadSerials(); }); // Save/Add serial $(document).on("click", "#saveSerialBtn", function () { const $input = $(".serial-input"); const serial = $.trim($input.val()); $input.val(""); if (!serial) return; let existingSerials = getAllSerials(); if (existingSerials.includes(serial)) { Notify("error", null, "Serial already exists"); return; } const input = $(``); $("#serial-inputs").append(input); appendSerial(serial); updateSerialCount(); }); // Get serials from hidden inputs function getAllSerials() { let serials = []; $(`input[name="stocks[${currentRowId}][serial_numbers][]"]`).each(function () { serials.push($(this).val()); }); return serials; } // Append a serial inside modal + hidden input function appendSerial(serial) { const $item = $(`
${serial} ×
`); $("#serialList").append($item); } // Load serials for current row (when modal opens) function loadSerials() { $("#serialList").html(""); let serials = getAllSerials(); serials.forEach(serial => { appendSerial(serial); }); updateSerialCount(); } // Update stock count in the row function updateSerialCount() { const count = $(`input[name="stocks[${currentRowId}][serial_numbers][]"]`).length; $(`#enteredCount`).text(count); $(`tr[data-row-id="${currentRowId}"]`).find('.productStock').val(count); } // Remove serial $(document).on("click", ".serial-remove-btn", function () { const $item = $(this).closest(".serial-item"); const serial = $item.find("span").first().text(); const rowId = $item.data("rowid"); $(`input[name="stocks[${rowId}][serial_numbers][]"][value="${serial}"]`).remove(); $item.remove(); updateSerialCount(); }); $(document).on("click", ".remove-row", function (e) { e.preventDefault(); const rowId = $(this).closest("tr").data('row-id'); $(`input[name="stocks[${rowId}][serial_numbers][]"]`).remove(); $('.' + rowId).remove(); $(this).closest("tr").remove(); }); $(document).on('click', '.remove-all-serial', function() { clearAllSerials(currentRowId); }); function clearAllSerials(clearRowId) { $(`input[name="stocks[${clearRowId}][serial_numbers][]"]`).remove(); $(`[data-rowid="${clearRowId}"]`).remove(); updateSerialCount(); } $(document).on('keydown', function (e) { if (e.key === "Enter") { if ($('#serialModal').is(':visible')) { e.preventDefault(); $('#serialModal').find('#saveSerialBtn').trigger('click'); } } }); $(document).ready(function () { // If edit page already has combo rows, calculate once if ($("#combo-product-rows .combo-row").length) { updateComboTotal(); } });