const { empty } = require("../Helpers/Common");
const Product = require("../models/product");
const Admin = require("../models/admin");
const moment = require("moment");
const crypto = require("crypto");
const { log } = require("util");
const { rfqEmail } = require("../constants/emailTemplates");
const sgMail = require('@sendgrid/mail')
const { SENDGRID_API_KEY, BASE_URL, portalName, SEND_EMAIL } = require("../constants/constant")
const Recipe = require("../models/recipe")
const Client = require('../models/client');
const axios = require('axios')
const Request = require("../models/request");
const JobCost = require("../models/jobCost");
const CustomerOrderProduct = require("../models/customerOrderProduct");
const CustomerOrder = require("../models/customerOrder");
const {
  clientId,
  clientSecret,
  redirectUri,
  authCode,
  companyId,
  accessToken,
  refreshToken,
  realmId,
  getNewAccessToken,
  PRODUCTION,
  catalogIncAccId,
  catalogExpAccId,
  materialIncAccId,
  materialAssetAccId,
  materialExpAccId,
  serviceIncAccId,
  serviceExpAccId,
  getAccounts,
  AVALARA_ACCOUNT_ID,
  AVALARA_LICENSE_KEY,
  AVALARA_BASE_URL,
  AVALARA_COMPANY_ID,
  findAccountIdByAcctNum,
  locationInventoryCode,
  departmentId,
  getAllAccountsWithGLCode,
} = require('../quickbook/quickbook.js');
const nodemailer = require('nodemailer');
const Project = require('../models/Projects');
const { validateInventoryItemsInQbForPo, validateVendorInQb, findItemByNameWithLocationCode, updateInvStockInQuickBooks } = require("./qbController.js");


const transporter = nodemailer.createTransport({
  host: 'mail.signwisesolutions.com', 
  port: 465, 
  secure: true, 
  auth: {
    user: 'noreply@signwisesolutions.com', 
    pass: 'signwise@2024', 
  },
});



//////////////////////////////////////quickbook



let quickbookId=null





// ///////////////////////////////////////////

// Helper function to search for existing product in QuickBooks
const findExistingProductInQuickBooks = async (productName) => {
  try {
    let token = await getNewAccessToken();
    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

    // Escape single quotes in product name for SQL query
    const escapedName = (productName + locationInventoryCode).replace(/'/g, "''");

    // Search for the product by name
    const searchResponse = await axios.get(
      `${apiUrl}/query?query=select * from Item where Name='${escapedName}'&minorversion=63`,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          Accept: 'application/json',
        },
      }
    );

    const product = searchResponse.data.QueryResponse.Item?.[0];

    if (product) {
      console.log("✅ Existing product found in QuickBooks:", product.Id);
      return product.Id;
    }
    return null;
  } catch (error) {
    console.error("Error searching for existing product:", error.response?.data || error.message);
    return null;
  }
};

const addProductToQuickBooks = async (productData) => {
  let token = getNewAccessToken();

  const postProduct = async (useNewToken = true) => {
    if (useNewToken) {
      console.log('Refreshing access token...');
      token = await getNewAccessToken();
    }

    try {
      const asOfDate = moment().format('YYYY-MM-DD');

      // ✅ Fetch proper account types
      // const incomeAccounts = await getAccounts('Income','SalesOfProductIncome');
      // const cogsAccounts = await getAccounts('Cost of Goods Sold');
      // const assetAccounts = await getAccounts('Other Current Asset');

      // ✅ Find the correct account types/subtypes for Inventory
      // const inventoryIncomeAcc = incomeAccounts.find(
      //   acc => acc.AccountSubType === 'SalesOfProductIncome'
      // );
      // const cogsExpenseAcc = cogsAccounts.find(
      //   acc => acc.AccountSubType === 'SuppliesMaterialsCogs'
      // );
      // const inventoryAssetAcc = assetAccounts.find(
      //   acc => acc.AccountSubType === 'Inventory'
      // );

      // ✅ Fallbacks if not found
      const dynamicIncomeAccountRef = await findAccountIdByAcctNum(4020000) //|| inventoryIncomeAcc ? inventoryIncomeAcc.Id : "193";
      const dynamicExpenseAccountRef = await findAccountIdByAcctNum(5041000) // cogsExpenseAcc ? cogsExpenseAcc.Id : "181";

      // ✅ Dynamic product type logic
      const productType =
        // ((productData.inhouse == 1 && productData.isTrafficCatalog == 0) || productData?.inhouse == 7)
         productData.inhouse == 4  ? "Inventory" : "NonInventory"

      // ✅ Trim name if too long
      let productName = productData.name.replace(/[:\t\n\r]+/g, "-").trim();
      if (productName.length > 95) {
        productName = productName.substring(0, 95);
      }

      const quickBooksProductData = {
        // Name: productData.name + locationInventoryCode,
        Name: productName + locationInventoryCode,
        Sku: productData.sku,
        Type: productType,
        IncomeAccountRef: {
          value: dynamicIncomeAccountRef,
        },
        ExpenseAccountRef: {
          value: dynamicExpenseAccountRef,
        },
        UnitPrice: productData.retailPrice,
        Description: productData.name.length > 95 ? `Product name : ${productData.name}\n\nDescription : ${productData.description || ''}` : productData.description || '',
      };

      // ✅ Add inventory-specific fields
      if (productType === "Inventory") {

        const dynamicAssetAccountRef = await findAccountIdByAcctNum(1013200) // inventoryAssetAcc ? inventoryAssetAcc.Id : "182";

        quickBooksProductData.TrackQtyOnHand = true;
        quickBooksProductData.QtyOnHand = 0;
        quickBooksProductData.InvStartDate = asOfDate;
        quickBooksProductData.PurchaseCost = productData.pf_cost_without_markup || 0;
        quickBooksProductData.AssetAccountRef = {
          value: dynamicAssetAccountRef,
        };
        // quickBooksProductData.ExpenseAccountRef = {
        //   value: dynamicExpenseAccountRef,
        // };
      }

      console.log("QuickBooks Product Data:", JSON.stringify(quickBooksProductData, null, 2));

      const searchLink = PRODUCTION
        ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item?minorversion=4`
        : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item?minorversion=4`;

      const response = await axios.post(searchLink, quickBooksProductData, {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          Accept: 'application/json'
        }
      });

      if (response.status === 200) {
        console.log("✅ Product added to QuickBooks:", response.data);
        quickbookId = response.data.Item.Id;
        return { success: true, data: response.data, quickbookId: quickbookId };
      } else {
        quickbookId = null;
        console.error("❌ Failed to add product:", response.data);
        return { success: false, error: response.data };
      }

    } catch (error) {
      // Check if it's a duplicate name error (code 6240)
      const errorData = error.response?.data;
      const isDuplicateError = errorData?.Fault?.Error?.some(
        err => err.code === "6240" || err.Message === "Duplicate Name Exists Error"
      );

      if (isDuplicateError) {
        console.log("⚠️ Duplicate product name detected. Searching for existing product...");

        // Try to extract ID from error detail first
        let existingId = null;
        const errorDetail = errorData?.Fault?.Error?.[0]?.Detail;
        if (errorDetail) {
          const idMatch = errorDetail.match(/Id=(\d+)/);
          if (idMatch) {
            existingId = idMatch[1];
            console.log("✅ Extracted existing product ID from error:", existingId);
          }
        }

        // If ID not found in error, search by name
        if (!existingId) {
          let productName = productData.name.replace(/:/g, "-");
          if (productName.length > 95) {
            productName = productName.substring(0, 95);
          }
          existingId = await findExistingProductInQuickBooks(productName);
        }

        if (existingId) {
          quickbookId = existingId;
          console.log("✅ Using existing QuickBooks product ID:", existingId);
          return {
            success: true,
            data: { Item: { Id: existingId } },
            isExisting: true,
            quickbookId: existingId
          };
        } else {
          console.error("❌ Could not find existing product ID");
          quickbookId = null;
          return { success: false, error: errorData || error.message, quickbookId: null };
        }
      } else {
        quickbookId = null;
        console.error("Error adding product:", JSON.stringify(errorData, null, 2));
        return { success: false, error: errorData || error.message, quickbookId: null };
      }
    }
  };

  return await postProduct();
};
//backup function
const syncAllCatalogProductsToQuickBooks = async (req, res) => {
  try {
    const limit = 50
    const type = 1
    const products = await Product.getAllCatalogProducts(limit);
    console.log('productsproducts', products)
    if (products.length == 0) {
      return res.send({ status: 1, message: "No products found to sync to QuickBooks" });
    }
    const failedProducts = [];
    let syncedProducts = [];
    for (const product of products) {
      // syncedProducts.push(product.name);
      // failedProducts.push(product.name);
      let quickbookId = null;
      if (type == 1) {
        const productDataForQB = {
          existingName: product?.name,
          name: product?.name,
          description: product?.description,
          retailPrice: product?.retail_price || 0,
          wholeSalePrice: product?.price || 0,
          sku: product?.vpt_number,
          cost: product?.retail_price ? product?.retail_price : 0,
          isTrafficCatalog: product?.isTrafficCatalog,
          inhouse: product?.inhouse,
          quickbookId: product?.quickbookId,
          unit_qty: product?.finishedProductQty,
          pf_cost_without_markup: (!product?.inhouse) ? product?.sub_contract_price : product?.pf_cost_without_markup || 0,
        };
        const result = await addProductToQuickBooks(productDataForQB);
        quickbookId = result?.quickbookId || null;
      } else {

        const productDataForQB = {
          existingName: product?.name,
          name: product?.name,
          retailPrice: product?.retail_price || 0,
          wholeSalePrice: product?.price || 0,
          sku: product?.vpt_number,
          cost: product?.retail_price ? product?.retail_price : 0,
          isTrafficCatalog: product?.isTrafficCatalog,
          inhouse: product?.inhouse,
          quickbookId: product?.quickbookId,
          unit_qty: product?.finishedProductQty,
          pf_cost_without_markup: (!product?.inhouse) ? product?.sub_contract_price : product?.pf_cost_without_markup,
        };
        await updateProductInQuickBooks(productDataForQB, product?.quickbookId);
        quickbookId = product?.quickbookId || null;
      }
      if (quickbookId) {
        let productData = {}
        productData.syncWithQuickbooks = 1
        if (type == 1) {
          productData.quickbookId = quickbookId
        }
        syncedProducts.push(product.name);
        let result = await Product.updateProduct(product.id, productData);
        if (result) {
          console.log('Product synced to QuickBooks : ', product.name);
        } else {
          console.log('Failed to sync product to QuickBooks : ', product.name);
        }
      } else {
        failedProducts.push(product.name);
      }
    }
    res.send({ status: 1, message: "Products synced to QuickBooks successfully", failedProducts: failedProducts, syncedProducts: syncedProducts });
  } catch (error) {
    console.log('syncAllCatalogProductsToQuickBooks error', error);
    res.send({ status: 0, message: "Failed to sync products to QuickBooks", error: error });
  }
}

function parseToNumberOrNull(value) {
  const num = parseFloat(value);
  return isNaN(num) ? null : num;
}

const addProduct = async (request, reply) => {
  try {
    const id = crypto.randomBytes(6).readUIntLE(0, 6);
    let barcode = String(id).padStart(14, '0');
    let currentDate = Math.round(moment().valueOf() / 1000);
    let productData = {};
    const retail_cost = parseFloat(request.body.retail_price?.value) || 0;
    const install_cost = parseFloat(request.body.install_cost?.value) || 0;
    const inHouseVal = request.body?.inHouse?.value;
    productData.name = request.body.name.value;
    productData.barcode = 'C' + barcode;
    productData.vpt_number = request?.body?.vpt_number?.value || null;
    productData.ul_number = request?.body?.ul_number?.value || null;
    const formattedDescription = (request.body.description.value || '').replace(/\r?\n/g, '\n');
    productData.description = formattedDescription;
    productData.description_lines = formattedDescription ? formattedDescription.split('\n') : [];
    const formattedNotes = (request.body.notes?.value || '').replace(/\r?\n/g, '\n');
    productData.notes = formattedNotes;
    productData.notes_lines = formattedNotes ? formattedNotes.split('\n') : [];
    // productData.price = request.body.inHouse?.value=='true' ? request.body.price.value : null;
    productData.retail_price = retail_cost + install_cost;
    productData.actual_retail_price = retail_cost;
    productData.install_cost = install_cost;
    productData.formula_cost = (request.body.inHouse?.value == 'true' || inHouseVal == 4 || inHouseVal == 5 || inHouseVal == 6 || inHouseVal == 7 || request.body?.isTrafficCatalog?.value == 'true') ? request.body.formula_cost.value : null;
    // productData.pf_retail_markup = request.body.inHouse?.value=='true' ? request.body.pf_retail_markup.value : null;
    productData.unit = request.body?.unit?.value
    productData.unit_qty = request?.body?.unit_qty?.value ? parseInt(request?.body?.unit_qty?.value) : null
    productData.tags = (request.body.tags?.value != '') ? request.body.tags?.value : null
    productData.category_id = parseInt(request.body.category?.value);
    productData.sub_category_id = parseInt(request.body.sub_category?.value);
    productData.workflow = parseInt(request.body.workflow?.value);
    productData.recipe_id = (request.body.inHouse?.value == 'true' || inHouseVal == 4 || inHouseVal == 5 || inHouseVal == 6 || inHouseVal == 7 || request.body?.isTrafficCatalog?.value == 'true') ? parseInt(request.body?.recipe?.value) : null;
    productData.special_price = request.body?.inHouse?.value == 'true' ? (!empty(request.body?.s_price?.value) ? request.body.s_price?.value : 0) : null;
    productData.start_date = request.body?.start?.value || null;
    // productData.min_value = '';
    // productData.max_value = '';
    productData.end_date = request.body?.end?.value || null;
    productData.timestamp = currentDate;
    productData.added_by = request.body?.added_by?.value;
    productData.status = 1;
    productData.isNew = 1;
    productData.isTrafficCatalog = request.body?.isTrafficCatalog?.value == 'true' ? 1 : 0
    productData.markup = parseToNumberOrNull(request.body?.markup?.value) || null
    productData.sub_contract_price = parseToNumberOrNull(request.body?.sub_contract_price?.value) || null
    productData.taxCodeId = parseToNumberOrNull(request.body?.taxCodeId?.value) || null
    productData.taxCode = request.body?.taxCode?.value || null
    productData.lead_time = parseToNumberOrNull(request.body?.lead_time?.value) || null
    productData.bin_location = request.body?.bin_location?.value || null

    if (parseInt(inHouseVal) != 1) {
      const rawStockNo = request.body?.in_stock_no?.value;
      productData.in_stock_no = rawStockNo && rawStockNo.trim() !== ''
        ? parseInt(rawStockNo)
        : null;
    } else {
      productData.in_stock_no = null;
    }

    if (request.body?.timeandmaterial?.value === 'true') {
      productData.inhouse = 2;
      productData.retail_price = request.body.sellingPrice.value;
    } else if (parseInt(inHouseVal) === 4) {
      productData.inhouse = 4;
    } else if (parseInt(inHouseVal) === 5) {
      productData.inhouse = 5;
    } else if (parseInt(inHouseVal) === 7) {
      productData.inhouse = 7;
    } else if (parseInt(inHouseVal) === 6) {
      productData.inhouse = 6;
    } else if (inHouseVal === 'true' || request.body?.isTrafficCatalog?.value === 'true') {
      productData.inhouse = 1;
    }
    else {
      productData.inhouse = 0;
    } // comment this if it causes any issue // comment this if it causes any issue

    // For sub-contract products (inhouse = 0), set retail_price equal to sub_contract_price
    if (productData.inhouse === 0 && productData.sub_contract_price) {
      productData.retail_price = productData.sub_contract_price;
    }
    if (productData.inhouse === 6) {
      productData.install_labour_quantity = request.body?.install_labour_quantity?.value || 0
      productData.facility_id = request.body?.facility_id?.value || null
    }

    // if(request.body?.timeandmaterial?.value=='true') {
    //   productData.inhouse=2
    //   // productData.price = request.body.sellingPrice.value
    //   productData.retail_price = request.body.sellingPrice.value
    // }else{

    //   productData.inhouse=(request.body?.inHouse?.value=='true' || request.body?.isTrafficCatalog?.value  == 'true') ? 1 : 0
    // } // uncomment this if it causes any error 


    /* ********************** */
    // console.log("$$$$$$$$$$$$$$$$$$$ productData : ", productData);
    /* ********************** */

    quickbookId = null

    const sampleProductData = {
      name: productData.name,
      unit_qty: productData.unit_qty ? productData.unit_qty : 0,
      cost: productData.special_price ? productData.special_price : 0,
      wholeSalePrice: request.body?.timeandmaterial?.value == 'true' ? request.body.sellingPrice.value : productData?.price || 0,
      retailPrice: request.body?.timeandmaterial?.value == 'true' ? request.body.sellingPrice.value : productData?.retail_price || 0,
      sku: productData.vpt_number,
      isTrafficCatalog: productData.isTrafficCatalog,
      inhouse: productData.inhouse,
      pf_cost_without_markup: (!productData.inhouse || productData.inhouse == 0 || productData.inhouse == "false") ? productData.sub_contract_price : request.body?.pf_cost_without_markup?.value || 0,
    };

    const [productNameExist, isServiceNameExist] = await Promise.all([
      Product.isProductNameExist(productData.name),
      Product.isServiceNameExist(productData.name)
    ]);
    // console.log("productNameExist : ", productNameExist);
    if (productNameExist != true && isServiceNameExist != true) {
      try {
        // Handle thumbnail URL from Supabase
        const thumbnailUrl = request.body?.thumbnail?.value || request.body?.thumbnail;
        if (thumbnailUrl) {
          productData.thumbnail = thumbnailUrl;
          console.log("Thumbnail URL set:", thumbnailUrl);
        }

        // const fullfilename = request.body?.thumbnail?.filename;
        // if (fullfilename) {
        //   const extension = fullfilename.substring(
        //     fullfilename.lastIndexOf(".") + 1,
        //     fullfilename.length
        //   );
        //   //   console.log("extension  ", extension);
        //   const newfilename = productData.name.replace(/[^a-zA-Z0-9 ]/g, "_");
        //   const filename = newfilename + "." + extension;
        //   //   console.log("filename-- ", filename);
        //   const parts = request.body?.thumbnail?.toBuffer();
        //   //   console.log("File ####################### : ", parts);
        //   //   console.log("Filename ####################### : ", filename);
        //   if (parts) {
        //     let fileNamewithDate = `${currentDate}_${filename}`;
        //     let uploaded = await Product.doUpload(parts, fileNamewithDate);
        //     // console.log("Filename : ", fileNamewithDate);
        //     if (uploaded) {
        //       //   console.log("uploaded response - ", uploaded);
        //       productData.thumbnail = fileNamewithDate;
        //       console.log("File uploaded successfully");
        //     } else {
        //       console.log("File upload error");
        //       reply.send({ status: 406, message: "File upload error" });
        //     }
        //   }

        let productResponse = null
        try {

          productResponse = await addProductToQuickBooks(sampleProductData);
        } catch (error) {
          console.log('err', error);

        }

        if (quickbookId) {
          productData.syncWithQuickbooks = 1,
            productData.quickbookId = quickbookId

        }


        let result = await Product.addProduct(productData);

        if (result?.id) {
          // Ensure facility_code is correctly fetched as a string
          const currentFacilityCode = String(request.body?.facility_code?.value || null);
          // console.log("currentFacilityCode", currentFacilityCode);

          // Correctly concatenate the facility_code with product ID, with a hyphen in between
          const updatedVptNumber = `${currentFacilityCode}-${result.id}`;

          // Update the product's vpt_number
          await Product.updateProduct(result.id, { vpt_number: updatedVptNumber });

          // Send the response with the updated vpt_number
          reply.send({
            status: 1,
            message: "Product has been successfully added!",
            product_id: result?.id || null,
            vpt_number: updatedVptNumber
          });
        }


        if (result?.id && productData.in_stock_no) {
          const logData = {
            catalog_source: 1, // 1 = Add Product
            catalog_id: result.id,
            added_by: productData.added_by,
            timestamp: currentDate,
            status: 1,
            catalog_log_message: `#${productData.in_stock_no} stock(s) added for product "${productData.name}" via Add Product`
          };

          await Product.addClientStockLog(logData); // model call
        }

        if (result) {

          // reply.send({
          //   status: 1,
          //   message: "Product has been successfully added!.",
          // });

          if (productResponse?.error) {
            return reply.send({
              status: 1,
              message: "Product added to DB but failed to add to QuickBooks.",
              quickBooksError: productResponse.error,
              product_id: result?.id || null,
            });
          } else {
            reply.send({
              status: 1,
              message: "Product has been successfully added!",
              quickBooksError: productResponse?.error,
              product_id: result?.id || null,
            });
          }

          if (request.body?.timeandmaterial?.value === 'true') {
            return;
          }

          // Notification
          let notificationMsg = `New product "${productData.name}" has been added.`;
          let notificationUrl = "products";

          // Alert to user of new added product

          const incrementResult_User = await Admin.incrementField(
            productData.added_by,
            "noti_count",
            1
          );
          //   console.log(
          //     "incrementResult--to--user-----------",
          //     incrementResult_User
          //   );
          let notificationData_user = {
            user_id: productData.added_by,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: productData.added_by,
          };
          //   console.log(
          //     "notificationData--user------------",
          //     notificationData_user
          //   );
          const addNotificationResult_user = await Admin.addNotification(
            notificationData_user
          );
          //   console.log(
          //     "addNotificationResult--user-------",
          //     addNotificationResult_user
          //   );

          // Alert to all admins of new added product

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};
          //   console.log("adminIds====", adminIds);
          for (const adminId of adminIds) {
            // console.log("adminId----------", adminId);
            if (adminId != productData.added_by) {
              const incrementResult = await Admin.incrementField(
                adminId,
                "noti_count",
                1
              );
              //   console.log("incrementResult-------------", incrementResult);
              notificationData = {
                user_id: adminId,
                url: notificationUrl,
                timestamp: currentDate,
                message: notificationMsg,
                status: 1,
                from_id: productData.added_by,
              };
              //   console.log("notificationData------------", notificationData);
              const addNotificationResult = await Admin.addNotification(
                notificationData
              );
              //   console.log(
              //     "addNotificationResult-------",
              //     addNotificationResult
              //   );
            }
          }
        } else {
          console.log("Failed to add the product ");
          reply.send({ status: 0, message: "Failed to add the product" });
        }
      } catch (error) {
        console.log("Failed to add the product : ", error);
        reply.send({ status: 0, message: "Failed to add the product" + error });
      }
    } else {
      console.log("The product already exist!");
      reply.send({ status: 2, message: "The product already exist!" });
    }
  } catch (error) {
    console.log("Server error", error);
    reply.status(500).send({ message: "Server error" });
  }
};

const getClientStockLogsController = async (request, reply) => {
  try {
    const { catalog_id } = request.body;

    const logs = await Product.getClientStockLogs(catalog_id);

    reply.send({
      status: 1,
      message: 'Client stock logs fetched successfully.',
      logs
    });
  } catch (error) {
    console.error("Controller Error - getClientStockLogs:", error);
    reply.send({
      status: 0,
      message: 'Failed to fetch client stock logs',
      error
    });
  }
};

const duplicateProduct = async (request, reply) => {
  try {
    let { id } = request.body;
    let currentDate = Math.round(moment().valueOf() / 1000);
    let products = await Product.getProductById(id);
    products = products[0]
    delete products.id;
    products.name = products.name + currentDate
    products.syncWithQuickbooks = 0
    products.quickbookId = null

    let notification = (products.meterial === 0) ? "Catelog has been duplicated!." : "Material has been duplicated!"
    let result = await Product.addProduct(products);

    // Generate the new vpt_number
    const currentFacilityCode = String(request.body?.facility_code?.value || 'FAC001'); // Default value if undefined
    const updatedVptNumber = `${currentFacilityCode}-${result.id}`;

    // Update the product's vpt_number
    await Product.updateProduct(result.id, { vpt_number: updatedVptNumber });

    if (result) {
      reply.send({
        status: 1,
        message: notification,
        vpt_number: updatedVptNumber
      });

      // Notification
      let notificationMsg = products.material == 0 ? "Catelog has been duplicate!." : "Material has been duplicated!"
      let notificationUrl = "products";

      // Alert to user of new added product

      const incrementResult_User = await Admin.incrementField(
        productData.added_by,
        "noti_count",
        1
      );
      //   console.log(
      //     "incrementResult--to--user-----------",
      //     incrementResult_User
      //   );
      let notificationData_user = {
        user_id: products.added_by,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: products.added_by,
      };
      //   console.log(
      //     "notificationData--user------------",
      //     notificationData_user
      //   );
      const addNotificationResult_user = await Admin.addNotification(
        notificationData_user
      );
      //   console.log(
      //     "addNotificationResult--user-------",
      //     addNotificationResult_user
      //   );

      // Alert to all admins of new added product

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      //   console.log("adminIds====", adminIds);
      for (const adminId of adminIds) {
        // console.log("adminId----------", adminId);
        if (adminId != productData.added_by) {
          const incrementResult = await Admin.incrementField(
            adminId,
            "noti_count",
            1
          );
          //   console.log("incrementResult-------------", incrementResult);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: products.added_by,
          };
          //   console.log("notificationData------------", notificationData);
          const addNotificationResult = await Admin.addNotification(
            notificationData
          );
          //   console.log(
          //     "addNotificationResult-------",
          //     addNotificationResult
          //   );
        }
      }
    } else {
      console.log("Failed to add the product ");
      reply.send({ status: 0, message: "Failed to add the product" });
    }

  } catch (error) {
    console.log("Server error", error);
    reply.status(500).send({ message: "Server error" });
  }
};

const getAllProducts = async (request, reply) => {
  try {
    const page = Number(request?.body?.page) || 1;
    const limit = Number(request?.body?.limit || request?.body?.rowsPerPage) || false;
    const search = request?.body?.search || '';
    const selectedTab = request?.body?.selectedTab || request?.body?.tab || null;
    const searchColumn = request?.body?.searchColumn || null;

    const productResponse = await Product.getAllFilteredProducts(true, false, {
      page,
      limit,
      search,
      selectedTab,
      searchColumn,
    });

    let results = productResponse?.results?.map(item => {
      const formattedDescription = (item.description || '').replace(/\r?\n/g, '\n');
      let sub_contract_retail_price = 0
      if (item?.inhouse === 0) {
        const price = Number(item?.sub_contract_price) || 0;
        const markup = Number(item?.markup) || 0;
        sub_contract_retail_price = price * (1 + markup / 100);
      }
      return {
        ...item,
        description: formattedDescription,
        description_lines: formattedDescription ? formattedDescription.split('\n') : [],
        sub_contract_retail_price: sub_contract_retail_price || 0
      };
    });

    reply.send({
      status: 1,
      results: results || [],
      pagination: productResponse?.pagination,
      totalCount: productResponse?.totalCount || 0,
      message: "Data fetched successfully"
    });
  } catch (error) {
    console.log("Server error", error);
    reply.status(500).send("Server error");
  }
};

const getProductById = async (request, reply) => {
  try {
    let { id } = request.query;
    let products = await Product.getProductById(id);
    products = products?.map(item => {
      const formattedDescription = (item.description || '').replace(/\r?\n/g, '\n');
      return {
        ...item,
        description: formattedDescription,
        description_lines: formattedDescription ? formattedDescription.split('\n') : [],
      };
    });
    // console.log("products************************", products);
    if (!empty(products)) {
      reply.send({ status: 1, result: products });
    } else {
      reply.send({ status: 0, message: "Product not found" });
    }
  } catch (error) {
    console.log(error);
    reply.status(500).send("Server error");
  }
};

//////////////////////////////////////Quickbook fun to update product/////////////////////////////
// const updateProductInQuickBooks = async (productData) => {
//   let token = accessToken;

//   const postUpdate = async (useNewToken = true) => {
//     if (useNewToken) {
//       console.log('Refreshing access token...');
//       token = await getNewAccessToken();
//     }

//     try {
//       const searchLink = PRODUCTION ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query?query=select * from Item where Name='${productData.existingName}'`
//       : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query?query=select * from Item where Name='${productData.existingName}'`;
//       // Step 1: Search for the product by name
//       const searchResponse = await axios.get(
//         searchLink,
//         {
//           headers: {
//             Authorization: `Bearer ${token}`,
//             'Content-Type': 'application/json',
//             Accept: 'application/json'
//           }
//         }
//       );

//       if (searchResponse.status === 200 && searchResponse.data.QueryResponse.Item && searchResponse.data.QueryResponse.Item.length > 0) {
//         const existingProduct = searchResponse.data.QueryResponse.Item[0];

//        let description = productData.wholeSalePrice ===0? "" :`Wholesale Price: ${productData.wholeSalePrice}, Retail Price: ${productData.retailPrice}`
//         const quickBooksProductData = {
//           ...existingProduct,
//           Name: productData.name,
//           UnitPrice: productData.retailPrice,
//           // Description: `Wholesale Price: ${productData.wholeSalePrice}, Retail Price: ${productData.retailPrice}`,
//           Type: "NonInventory",
//           Sku: productData.sku,
//           // Description: description,
//           sparse: true ,
//           UnitPrice: productData.retailPrice,

//         };

//         console.log("QuickBooks Product Data for Update:", JSON.stringify(quickBooksProductData, null, 2));

//         const updateLink = PRODUCTION ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item?operation=update&minorversion=4`
//                                       : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item?operation=update&minorversion=4`;
//         // Step 3: Make the update request
//         const updateResponse = await axios.post(
//           updateLink,
//           quickBooksProductData,
//           {
//             headers: {
//               Authorization: `Bearer ${token}`,
//               'Content-Type': 'application/json',
//               Accept: 'application/json'
//             }
//           }
//         );

//         if (updateResponse.status === 200) {
//           console.log("Product has been successfully updated in QuickBooks:", updateResponse.data);
//           quickbookId = updateResponse.data.Item.Id;
//         } else {
//           console.error("Failed to update the product in QuickBooks:", updateResponse.data);
//         }
//       } else {
//         console.log(`Product with name '${productData.name}' not found in QuickBooks.`);
//       }
//     } catch (error) {
//       console.error("Error updating product in QuickBooks:", error.response ? error.response.data : error.message);
//       const faultType = error.response?.data?.Fault?.type;
//       if (faultType === 'AUTHENTICATION') {
//         console.log('Access token expired, refreshing token...');
//         try {
//           // Retry the API call with a new access token
//           await postUpdate(true);
//         } catch (refreshError) {
//           console.error('Error refreshing token:', refreshError.response?.data || refreshError.message);
//         }
//       } else if (faultType === 'ValidationFault') {
//         console.error("Validation error updating product in QuickBooks:", JSON.stringify(error.response?.data, null, 2));
//       } else {
//         console.error("Other error updating product in QuickBooks:", error.response?.data || error.message);
//       }
//     }
//   };

//   await postUpdate();
// };



const updateProductInQuickBooks = async (productData) => {
  let token = accessToken;

  const postUpdate = async (useNewToken = true) => {
    if (useNewToken) {
      console.log("Refreshing access token...");
      token = await getNewAccessToken();
    }

    try {
      // 🟢 Step 1: Fetch existing product by QuickBooks ID instead of name
      const readLink = PRODUCTION
        ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item/${productData.quickbookId}`
        : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item/${productData.quickbookId}`;

      const readResponse = await axios.get(readLink, {
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
          Accept: "application/json",
        },
      });

      if (readResponse.status === 200 && readResponse.data.Item) {
        const existingProduct = readResponse.data.Item;

        // ✅ Fallbacks if not found
        const dynamicIncomeAccountRef = await findAccountIdByAcctNum(4020000) //|| inventoryIncomeAcc ? inventoryIncomeAcc.Id : "193";
        const dynamicExpenseAccountRef = await findAccountIdByAcctNum(5041000) // cogsExpenseAcc ? cogsExpenseAcc.Id : "181";

        // ✅ Dynamic product type logic
        const productType =  productData.inhouse == 4  ? "Inventory" : "NonInventory"
          // ((productData.inhouse == 1 && productData.isTrafficCatalog == 0) || productData?.inhouse == 7)
          //   ? "NonInventory"
          //   : "Inventory";

        let description =
          productData.wholeSalePrice === 0
            ? ""
            : `Wholesale Price: ${productData.wholeSalePrice}, Retail Price: ${productData.retailPrice}`;

        let productName = productData.name.replace(/[:\t\n\r]+/g, "-").trim();
        if (productName.length > 95) {
          productName = productName.substring(0, 95);
        }
        // 🟢 Step 2: Build QuickBooks update payload
        const quickBooksProductData = {
          ...existingProduct,
          Type: productType,
          sparse: true, // Required for partial update
          // Name: productData.name + locationInventoryCode,
          Name: productName + locationInventoryCode,
          Sku: productData.sku,
          // Type: "NonInventory", // Keeping existing type logic
          UnitPrice: productData.retailPrice,
          // Description: description,
          IncomeAccountRef: {
            value: dynamicIncomeAccountRef,
          },
          ExpenseAccountRef: {
            value: dynamicExpenseAccountRef,
          },
        };

        if (productType === "Inventory") {

          const dynamicAssetAccountRef = await findAccountIdByAcctNum(1013200) // inventoryAssetAcc ? inventoryAssetAcc.Id : "182";

          quickBooksProductData.TrackQtyOnHand = true;
          quickBooksProductData.QtyOnHand = productData?.unit_qty || existingProduct?.QtyOnHand || 0;
          quickBooksProductData.PurchaseCost = productData.pf_cost_without_markup || 0;
          quickBooksProductData.InvStartDate = existingProduct?.InvStartDate || moment().format('YYYY-MM-DD');
          quickBooksProductData.AssetAccountRef = {
            value: dynamicAssetAccountRef,
          };
        }

        console.log(
          "QuickBooks Product Data for Update:",
          JSON.stringify(quickBooksProductData, null, 2)
        );

        // 🟢 Step 3: Make the update request
        const updateLink = PRODUCTION
          ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item?operation=update&minorversion=4`
          : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item?operation=update&minorversion=4`;

        const updateResponse = await axios.post(updateLink, quickBooksProductData, {
          headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
            Accept: "application/json",
          },
        });

        // 🟢 Step 4: Handle response
        if (updateResponse.status === 200) {
          console.log(
            "✅ Product has been successfully updated in QuickBooks:",
            updateResponse.data
          );
          quickbookId = updateResponse.data.Item.Id;
        } else {
          console.error("❌ Failed to update the product in QuickBooks:", updateResponse.data);
        }
      } else {
        console.log(
          `⚠️ Product with ID '${productData.quickbookId}' not found in QuickBooks.`
        );
      }
    } catch (error) {
      console.error(
        "Error updating product in QuickBooks:",
        error.response ? error.response.data : error.message
      );

      const faultType = error.response?.data?.Fault?.type;
      if (faultType === "AUTHENTICATION") {
        console.log("Access token expired, refreshing token...");
        try {
          await postUpdate(true);
        } catch (refreshError) {
          console.error(
            "Error refreshing token:",
            refreshError.response?.data || refreshError.message
          );
        }
      } else if (faultType === "ValidationFault") {
        console.error(
          "Validation error updating product in QuickBooks:",
          JSON.stringify(error.response?.data, null, 2)
        );
      } else {
        console.error(
          "Other error updating product in QuickBooks:",
          error.response?.data || error.message
        );
      }
    }
  };

  await postUpdate();
};








const getAllGLCodes = async () => {
  const token = await getNewAccessToken();

  const url = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query?query=SELECT * FROM Account &minorversion=65`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query?query=SELECT * FROM Account `;

  const headers = {
    "Authorization": `Bearer ${token}`,
    "Accept": "application/json",
    "Content-Type": "application/json"
  };

  try {
    const response = await axios.get(url, { headers });
    const accounts = response.data.QueryResponse.Account || [];
    // Return accounts that have a valid GL Code (AcctNum)
    return accounts
      .filter(account => account.AcctNum && account.AcctNum.trim() !== "")
      .map(account => ({
        id: account.Id,
        name: account.Name,
        glCode: String(account.AcctNum),
        type: account.AccountType,
        classification: account.Classification,
        detailType: account.AccountSubType,  // Adding the DetailType here
        accountType: account.AccountType // Adding the AccountType here
      }));

  } catch (error) {
    console.error("❌ Error fetching GL Codes:", error.response?.data.Fault.Error || error.message);
    return [];
  }
};
const getAllAccountsGLCodes = async (account, detailType) => {
  const token = await getNewAccessToken();

  let accountSubType = ''
  if (detailType) {
    accountSubType = `AND AccountSubType = '${detailType}'`;
  }
  const url = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query?query=SELECT * FROM Account WHERE AccountType = '${account}' ${accountSubType}&minorversion=65`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query?query=SELECT * FROM Account WHERE AccountType = '${account}' ${accountSubType}&minorversion=65`;

  const headers = {
    "Authorization": `Bearer ${token}`,
    "Accept": "application/json",
    "Content-Type": "application/json"
  };

  try {
    const response = await axios.get(url, { headers });
    const accounts = response.data.QueryResponse.Account || [];
    // Return accounts that have a valid GL Code (AcctNum)
    return accounts
      .filter(account => account.AcctNum && account.AcctNum.trim() !== "")
      .map(account => ({
        id: account.Id,
        name: account.Name,
        glCode: String(account.AcctNum),
        type: account.AccountType,
        classification: account.Classification,
        detailType: account.AccountSubType,  // Adding the DetailType here
        accountType: account.AccountType // Adding the AccountType here
      }));

  } catch (error) {
    console.error("❌ Error fetching GL Codes:", error.response?.data.Fault.Error || error.message);
    return [];
  }
};

// (async ()=>{
//   const result = await getAllAccountByGLCodes('7000');
// console.log('getAllAccountsGLCodes',result);
// })()

const findAccountByGlCode = async (glCode) => {
  if (!glCode) return null; // Return null if no GL Code is provided

  try {
    // Fetch all accounts from QuickBooks (with pagination) that have GL Codes
    const allAccounts = await getAllAccountsWithGLCode();

    const matchedAccount = allAccounts.find((account) => account.glCode == glCode);
    // console.log('matchedAccount', matchedAccount, allAccounts)
    return matchedAccount ? matchedAccount.id : null; // Return ID if found, else null
  } catch (error) {
    console.error(`Error finding account for GL Code (${glCode}):`, error.message);
    return null;
  }
};

async function run() {
  let rel = await findAccountByGlCode(7000);
  console.log('relrelrel', rel);
}

// run();

const updateMaterialInQuickBooks = async (materialProductData) => {
  let token = accessToken;

  const postUpdateMaterial = async (useNewToken = true) => {
    if (useNewToken) {
      console.log('Refreshing access token...');
      token = await getNewAccessToken();
    }

    try {
      const queryParam = `select * from Item where Id='${materialProductData.quickbookId}'`;
      // const searchUrl = PRODUCTION 
      // ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query?query=select * from Item where Name='${materialProductData.existingName}'`
      // : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query?query=select * from Item where Name='${materialProductData.existingName}'`;
      const searchUrl = PRODUCTION
        ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query?query=${encodeURIComponent(queryParam)}`
        : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query?query=${encodeURIComponent(queryParam)}`;

      // Step 1: Search for the material by name
      const searchResponse = await axios.get(
        searchUrl,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
            Accept: 'application/json',
          },
        }
      );

      if (searchResponse.status === 200 && searchResponse.data.QueryResponse.Item && searchResponse.data.QueryResponse.Item.length > 0) {
        const existingMaterial = searchResponse.data.QueryResponse.Item[0];



        // Fetch dynamic account IDs based on account types
        // const assetAccounts = await getAccounts('Other Current Asset','Inventory');   // Fetch asset accounts
        const expenseAccounts = await getAccounts('Cost of Goods Sold', 'SuppliesMaterialsCogs'); // Fetch expense accounts
        // const incomeAccounts = await getAccounts('Income', 'SalesOfProductIncome'); 
        const incomeAccounts = await getAccounts('Income', 'SalesOfProductIncome');

        const filteredIncomeAcc = incomeAccounts?.find(acc =>
          acc.FullyQualifiedName?.toLowerCase().includes('sales') ||
          acc.Name?.toLowerCase().includes('product') ||
          acc.AccountSubType === 'SalesOfProductIncome'
        );


        // Use the first account for each type (if available)
        // const materialIncAccIdNew = incomeAccounts?.length > 0 ? incomeAccounts[0].Id : null;
        let dynamicMaterialAssetAccId = await findAccountIdByAcctNum(1013200) // assetAccounts?.length > 0 ? assetAccounts[0].Id : null;
        let dynamicMaterialExpAccId = expenseAccounts?.length > 0 ? expenseAccounts[0].Id : null;
        // let dynamicMaterialIncAccId = incomeAccounts?.length > 0 ? incomeAccounts[0].Id : null;
        let dynamicMaterialIncAccId = incomeAccounts?.length > 0 ? filteredIncomeAcc.Id : null;
        let asOfDate = moment().format('YYYY-MM-DD');

        if (materialProductData?.incomeGlCode) {
          dynamicMaterialIncAccId = await findAccountByGlCode(materialProductData?.incomeGlCode)
        } else {
          dynamicMaterialIncAccId = await findAccountIdByAcctNum(4020000) //|| expenseAccounts?.length > 0 ? expenseAccounts[0].Id : null;
        }
        if (materialProductData?.expenseGlCode) {
          dynamicMaterialExpAccId = await findAccountByGlCode(materialProductData?.expenseGlCode)
        } else {
          dynamicMaterialExpAccId = await findAccountIdByAcctNum(5041000) //|| expenseAccounts?.length > 0 ? expenseAccounts[0].Id : null;
        }


        // ✅ Determine QuickBooks Item Type Based on itemType
        let itemTypeMap = {
          1: "Inventory",      // Inventory
          // 1: "NonInventory",      // Inventory
          2: "NonInventory",   // Non-Inventory
          3: "Service",        // Service
          4: "Group"           // Bundle (QuickBooks uses "Group" for bundles)
        };
        let qbType = itemTypeMap[materialProductData.itemType] || "Inventory";

        let track = true
        if (qbType == "Inventory" || materialProductData.itemType == 1) {
          track = true
        } else {
          track = false
        }

        let productName = materialProductData.name.replace(/[:\t\n\r]+/g, "-").trim();
        if (productName.length > 95) {
          productName = productName.substring(0, 95);
        }
        // Prepare the updated material product data
        const quickBooksMaterialData = {
          ...existingMaterial,
          // Name: materialProductData.name + locationInventoryCode,
          Name: productName + locationInventoryCode,
          Type: qbType,
          TrackQtyOnHand: track,
          UnitPrice: materialProductData.price,
          PurchaseCost: materialProductData.price,
          Description: materialProductData.description || '',
          QtyOnHand: materialProductData.unit_qty || existingMaterial?.QtyOnHand || 0,
          Sku: materialProductData.sku,
          ReorderPoint: materialProductData.ReorderPoint,
          sparse: true, // Sparse update to ensure only specified fields are updated
          InvStartDate: asOfDate,
          IncomeAccountRef: {
            value: dynamicMaterialIncAccId
          },
          AssetAccountRef: {
            value: dynamicMaterialAssetAccId
          },
          ExpenseAccountRef: {
            value: dynamicMaterialExpAccId
          },

        };

        // ✅ Handle Category & Subcategory Assignments Correctly
        if (materialProductData.qb_sub_category && materialProductData.qb_sub_category !== "null") {
          quickBooksMaterialData.SubItem = true;
          quickBooksMaterialData.ParentRef = { value: String(materialProductData.qb_sub_category) };
        } else if (materialProductData.qb_category && materialProductData.qb_category !== "null") {
          quickBooksMaterialData.SubItem = true; // Required for QuickBooks to recognize the parent-child relationship
          quickBooksMaterialData.ParentRef = { value: String(materialProductData.qb_category) };
        } else {
          // If no category or subcategory, remove ParentRef & SubItem
          delete quickBooksMaterialData.ParentRef;
          delete quickBooksMaterialData.SubItem;
        }

        console.log("QuickBooks Material Data for Update:", JSON.stringify(quickBooksMaterialData, null, 2));

        const updateUrl = PRODUCTION
          ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item?operation=update&minorversion=4`
          : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item?operation=update&minorversion=4`;
        // Step 3: Make the update request
        const updateResponse = await axios.post(
          updateUrl,
          quickBooksMaterialData,
          {
            headers: {
              Authorization: `Bearer ${token}`,
              'Content-Type': 'application/json',
              Accept: 'application/json',
            },
          }
        );

        if (updateResponse.status === 200) {
          console.log("Material has been successfully updated in QuickBooks:", updateResponse.data);
          quickbookId = updateResponse.data.Item.Id;
        } else {
          console.error("Failed to update the material in QuickBooks:", updateResponse.data);
        }
      } else {
        console.log(`Material with name '${materialProductData.name}' not found in QuickBooks.`);
      }
    } catch (error) {
      console.error("Error updating material in QuickBooks:", error.response ? error.response.data : error.message);
      const faultType = error.response?.data?.Fault?.type;
      if (faultType === 'AUTHENTICATION') {
        console.log('Access token expired, refreshing token...');
        try {
          // Retry the API call with a new access token
          await postUpdateMaterial(true);
        } catch (refreshError) {
          console.error('Error refreshing token:', refreshError.response?.data || refreshError.message);
        }
      } else if (faultType === 'ValidationFault') {
        console.error("Validation error updating material in QuickBooks:", JSON.stringify(error.response?.data, null, 2));
      } else {
        console.error("Other error updating material in QuickBooks:", error.response?.data || error.message);
      }
    }
  };

  await postUpdateMaterial();
};

const sumQuantities = (materialInventories) => {
  const totalQty = materialInventories?.reduce((acc, inventory) => {
    return acc + (inventory.qty || 0);
  }, 0);

  console.log('Total quantity:', totalQty);
  return totalQty;
};


/////////////////////////////////////////////////////////////////////////////

const updateProduct = async (request, reply) => {
  try {
    const qrid = crypto.randomBytes(6).readUIntLE(0, 6);
    let barcode = String(qrid).padStart(14, '0');
    let currentDate = Math.round(moment().valueOf() / 1000);
    let productData = {};
    let { id } = request.query;
    let oldProductData = await Product.getProductById(id);
    let replyMsg = oldProductData[0]?.meterial == 1 ? `Material` : `Product`;

    if (!oldProductData || oldProductData.length === 0) {
      return reply.send({ status: 404, message: "Product not found" });
    }

    // Map request body data to productData object
    const {
      name, facility_id, install_labour_quantity, description, install_cost, unit, material_types, tags, min_value, max_value, unit_qty, tax,
      price, inHouse, retail_price, formula_cost, s_price, category, sub_category, workflow,
      recipe, start, end, added_by, vpt_number, vendor_id, secondary_vendor_id, secondary_sku, pf_retail_markup, qb_category, qb_sub_category,
      account_type, income_account, expense_account, lead_time, isTrafficCatalog, taxable, markup, sub_contract_price, taxCodeId, taxCode, in_stock_no, prod_inhouse_no, productQuickbookId, ul_number
    } = request.body;
    // console.log("HDFIHDBFIDBIBFDIFIDB", request.body);

    productData.name = name?.value;
    const formattedDescription = (description?.value || '').replace(/\r?\n/g, '\n');
    productData.description = formattedDescription;
    productData.description_lines = formattedDescription ? formattedDescription.split('\n') : [];
    const formattedNotes = (request.body.notes?.value || '').replace(/\r?\n/g, '\n');
    productData.notes = formattedNotes;
    productData.notes_lines = formattedNotes ? formattedNotes.split('\n') : [];
    if (vpt_number?.value) {
      productData.vpt_number = vpt_number?.value
    }
    productData.ul_number = (ul_number?.value ?? '').toString().trim() || null;
    if (vendor_id?.value) {
      productData.vendor_id = vendor_id?.value
    }
    // Secondary vendor and SKU handling
    if (secondary_vendor_id?.value) {
      productData.secondary_vendor_id = secondary_vendor_id?.value
    }
    if (secondary_sku?.value) {
      productData.secondary_sku = secondary_sku?.value
    }
    if (!oldProductData[0]?.barcode) {
      productData.barcode = 'C' + barcode;
    }
    productData.unit = unit?.value;
    productData.material_types = material_types?.value;
    productData.tags = tags?.value || null;
    productData.min_value = parseInt(min_value?.value);
    productData.max_value = parseInt(max_value?.value);
    productData.unit_qty = unit_qty?.value ? parseInt(unit_qty.value) : null;
    productData.tax_category = tax?.value || null;
    productData.taxable = taxable?.value || 0;
    productData.markup = parseToNumberOrNull(markup?.value) || null
    productData.sub_contract_price = parseToNumberOrNull(sub_contract_price?.value) || null
    productData.in_stock_no = in_stock_no?.value || null;
    productData.taxCodeId = parseToNumberOrNull(request.body?.taxCodeId?.value) || null
    productData.taxCode = request.body?.taxCode?.value || null
    productData.in_stock_no = in_stock_no?.value || null;
    productData.lead_time = lead_time?.value || null;
    productData.bin_location = request.body?.bin_location?.value || null;

    // productData.qb_category = qb_category.value || null;
    // productData.qb_sub_category = qb_sub_category.value || null;

    productData.account_type = account_type?.value || '';


    productData.qb_category = (qb_category?.value && qb_category?.value != 'null') ? qb_category?.value : null;
    productData.qb_sub_category = (qb_sub_category?.value && qb_sub_category?.value != 'null') ? qb_sub_category?.value : null;

    // productData.qb_income_account= (income_account?.value && income_account?.value != 'null') ? income_account?.value : null;
    // productData.qb_expense_account = (expense_account?.value && expense_account?.value != 'null') ? expense_account?.value : null;

    productData.qb_income_account =
      (income_account?.value && income_account?.value !== 'null' && income_account?.value !== 'undefined')
        ? income_account?.value
        : null;

    productData.qb_expense_account =
      (expense_account?.value && expense_account?.value !== 'null' && expense_account?.value !== 'undefined')
        ? expense_account?.value
        : null;


    if (oldProductData[0]?.meterial == 1) {
      productData.price = price?.value || null;
      productData.special_price = price?.value || null;
    } else {
      // productData.price = inHouse?.value === 'true' ? (price?.value || null) : 0;
      productData.special_price = (inHouse?.value === 'true' || isTrafficCatalog?.value == "true" || parseInt(prod_inhouse_no?.value) === 6) ? (s_price?.value || null) : 0;
      productData.actual_retail_price = retail_price?.value || 0;
    }

    const retail_cost = parseFloat(retail_price?.value) || 0;
    const install_co = parseFloat(install_cost?.value) || 0;
    productData.retail_price = (retail_cost + install_co) || 0;
    // productData.actual_retail_price = retail_price?.value || 0;
    productData.install_cost = install_cost?.value || 0;
    productData.formula_cost = (inHouse?.value === 'true' || oldProductData[0]?.meterial == 1 || isTrafficCatalog?.value === "true" || parseInt(prod_inhouse_no?.value) === 6) ? (formula_cost?.value || null) : 0;
    // productData.pf_retail_markup = (inHouse?.value === 'true') ? (Number(pf_retail_markup?.value) || null) : 0;
    productData.category_id = parseInt(category?.value);
    productData.sub_category_id = parseInt(sub_category?.value);
    productData.workflow = parseInt(workflow?.value);
    productData.recipe_id = (inHouse?.value === 'true' || isTrafficCatalog?.value === "true" || parseInt(prod_inhouse_no?.value) === 6) ? parseInt(recipe?.value) : null;
    productData.start_date = start?.value || null;
    productData.end_date = end?.value || null;
    productData.timestamp = currentDate;
    productData.added_by = added_by?.value;
    if (oldProductData[0]?.meterial == 0) {
      productData.inhouse = (inHouse?.value === 'true' || isTrafficCatalog?.value === "true") ? 1 : 0;
    }
    productData.status = 1;

    if (request.body?.timeandmaterial?.value == 'true') {
      productData.inhouse = 2
      // productData.price = request.body.sellingPrice.value
      productData.retail_price = request.body.sellingPrice.value
    }

    if (oldProductData[0]?.meterial == 0) {

      productData.inhouse = prod_inhouse_no?.value !== undefined ? parseInt(prod_inhouse_no.value) : 0;
      // productData.inhouse = prod_inhouse_no?.value || null;
    }
    const [productNameExist, isServiceNameExist] = await Promise.all([
      Product.isProductNameExist(productData.name, id),
      Product.isServiceNameExist(productData.name)
    ]);

    if (!productNameExist && !isServiceNameExist) {
      try {
        // Handle thumbnail URL from Supabase
        const thumbnailUrl = request.body?.thumbnail?.value || request.body?.thumbnail;
        if (thumbnailUrl) {
          productData.thumbnail = thumbnailUrl;
          // console.log("Thumbnail URL updated:", thumbnailUrl);
        }

        // const thumbnail = request.body?.thumbnail && request.body?.thumbnail?.value || 'null';
        // if (thumbnail !== 'null') {
        //   const fullfilename = thumbnail?.filename;
        //   const extension = fullfilename.substring(fullfilename.lastIndexOf(".") + 1);
        //   const newfilename = productData.name.replace(/[^a-zA-Z0-9 ]/g, "_");
        //   const filename = `${newfilename}.${extension}`;
        //   const fileParts = thumbnail?.toBuffer();

        //   if (fileParts) {
        //     let fileNamewithDate = `${currentDate}_${filename}`;
        //     let uploaded = await Product.doUpload(fileParts, fileNamewithDate);
        //     if (uploaded) {
        //       productData.thumbnail = fileNamewithDate;
        //       console.log("File uploaded successfully");
        //     } else {
        //       console.log("File upload error");
        //       return reply.send({ status: 406, message: "File upload error" });
        //     }
        //   }
        /////////////////////////////////////////////////////qb
        quickbookId = null
        let qbErr = null

        try {
          if (oldProductData[0]?.meterial == 1) {
            const materialInventoryData = await Product.getMaterialInventoryByMaterialId(oldProductData[0].id)

            let materialResult = 0;
            let finishedProductQty = 0;
            if (materialInventoryData) {
              materialResult = sumQuantities(materialInventoryData);
              let filteredMaterialInventoryData = materialInventoryData.filter(item => item.partial == 0);
              finishedProductQty = sumQuantities(filteredMaterialInventoryData); //meed both finished and partial stock
            }
            // console.log('dataaaaaaaaaaaaaaaaaaaaaaaa', materialResult);

            const materialProductData = {
              existingName: oldProductData[0]?.name,
              name: productData?.name,
              description: productData?.description,
              price: productData?.special_price || 0,
              unit_qty: finishedProductQty || 0,
              qb_category: productData.qb_category,
              qb_sub_category: productData.qb_sub_category,
              sku: productData.vpt_number,
              ReorderPoint: productData.min_value,
              itemType: productData?.material_types || 1,
              incomeGlCode: productData.qb_income_account,
              expenseGlCode: productData.qb_expense_account,
              quickbookId: oldProductData[0]?.quickbookId,

            };

        // if(oldProductData[0].syncWithQuickbooks !==1 || oldProductData[0].quickbookId == null){
        if(oldProductData[0].syncWithQuickbooks !==1){
          // productData.syncWithQuickbooks=1,
          const item = await findItemByNameWithLocationCode(productData?.name);
          // console.log("Checking item exist in quickbooks...............",item)
          if(item){
            productData.quickbookId = item,
            productData.syncWithQuickbooks=1
          }else{
            console.log("continue adding material in quickbooks...............")
          await addMaterialToQuickBooks(materialProductData)
          if(quickbookId){
            productData.syncWithQuickbooks=1,
            productData.quickbookId=quickbookId
          }
        }
        }else {
          const maaterialValidationResult = await validateInventoryItemsInQbForPo(materialProductData,"quickbookId");
          // console.log("material validation result in quickbooks...............",maaterialValidationResult)
          if(maaterialValidationResult.itemData){
           productData.syncWithQuickbooks=0,
           productData.quickbookId=null
          }else{
            // console.log("continue updating material in quickbooks...............")
            await updateMaterialInQuickBooks(materialProductData)
          }
            if(quickbookId){
              productData.syncWithQuickbooks=1,
              productData.quickbookId=quickbookId
            }
        }
      }else{

            const materialInventoryData = await Product.getMaterialInventoryByMaterialId(oldProductData[0].id)

            let materialResult = 0;
            let finishedProductQty = 0;
            if (materialInventoryData) {
              materialResult = sumQuantities(materialInventoryData);
              let filteredMaterialInventoryData = materialInventoryData.filter(item => item.partial == 0);
              finishedProductQty = sumQuantities(filteredMaterialInventoryData);
            }

            const productDataForQB = {
              existingName: oldProductData[0]?.name,
              name: productData?.name,
              retailPrice: request.body?.timeandmaterial?.value == 'true' ? request.body.sellingPrice.value : productData?.retail_price || 0,
              wholeSalePrice: request.body?.timeandmaterial?.value == 'true' ? request.body.sellingPrice.value : productData?.price || 0,
              sku: productData.vpt_number,
              cost: productData.special_price ? productData.special_price : 0,
              isTrafficCatalog: productData.isTrafficCatalog || isTrafficCatalog?.value === "true" ? true : false,
              inhouse: productData.inhouse,
              quickbookId: productQuickbookId?.value,
              pf_cost_without_markup: (!productData.inhouse || productData.inhouse == 0 || productData.inhouse == "false") ? productData.sub_contract_price : request.body?.pf_cost_without_markup?.value || 0,
              unit_qty: finishedProductQty || 0,
            };

        if(oldProductData[0].syncWithQuickbooks !==1){
          const item = await findItemByNameWithLocationCode(productData?.name);
          // console.log("Checking item exist in quickbooks...............",item)
          if(item){
            productData.quickbookId = item,
            productData.syncWithQuickbooks=1
          }else{
            // console.log("continue adding catalog in quickbooks...............")
          await addProductToQuickBooks(productDataForQB);
          if(quickbookId){
            productData.syncWithQuickbooks=1,
            productData.quickbookId=quickbookId
          }
        }
        }else{
          const catalogValidationResult = await validateInventoryItemsInQbForPo(productDataForQB,"quickbookId");
          // console.log("catalog validation result in quickbooks...............",catalogValidationResult)
         if(catalogValidationResult.itemData){
          productData.syncWithQuickbooks=0,
          productData.quickbookId=null
         }else{
          // console.log("continue updating catalog in quickbooks...............")
           await updateProductInQuickBooks(productDataForQB);
         }
          if(quickbookId){
            productData.syncWithQuickbooks=1,
            productData.quickbookId=quickbookId
          }
        }
      }
    } catch (quickBooksError) {
      console.error("QuickBooks error:", quickBooksError);
      qbErr = quickBooksError
    }

        // For sub-contract products (inhouse = 0), set retail_price and actual_retail_price equal to sub_contract_price
        if (productData.inhouse === 0 && productData.sub_contract_price) {
          productData.retail_price = productData.sub_contract_price;
          productData.actual_retail_price = productData.sub_contract_price;
        }
        if (productData.inhouse === 6) {
          productData.facility_id = facility_id?.value || null
          productData.install_labour_quantity = install_labour_quantity?.value || 0
        }
        // Clear the persisted highlight once a catalog is edited
        productData.isNew = 0;

        ///////////////////////////////////////////////////
        let updated = await Product.updateProduct(id, productData);
        if (updated) {

          const oldStock = oldProductData[0]?.in_stock_no;
          const newStock = productData.in_stock_no;

          if (oldStock || newStock) {
            let message = `Catalog stock updated for product "${productData.name}" `;

            if (oldStock && newStock && oldStock !== newStock) {
              message += `(Previous: ${oldStock}, New Total: ${newStock}).`;
            } else if (!oldStock && newStock) {
              message += ` Stock set to ${newStock}.`;
            } else if (oldStock && !newStock) {
              message += ` Stock was removed (previously ${oldStock}).`;
            }

            // await Product.addClientStockLog({
            //   catalog_id: id,
            //   catalog_log_message: message,
            //   timestamp: currentDate,
            //   status: 1,
            //   catalog_source: 2, // 2 = update
            //   added_by: productData.added_by, // ✅ Required
            // });
          }

          let allRecipies = await Product.fetchRecipesBasedOnMaterial(id);
          // Optimize recipe updates with parallel execution
          await Promise.all(
            allRecipies.map(async (recipeRef) => {
              const recipeData = await Recipe.getRecipeByIdForAutoUpdate(recipeRef.recipe_id);
              const recipe = recipeData?.[0];
              
              // Skip if recipe not found
              if (!recipe) return;

              const ingredients = recipe.ingredients || [];
              const services = recipe.services || [];

              // Calculate expected material cost (without markup)
              const expectedMaterialCost = ingredients.reduce((accumulator, currentValue) => {
                return accumulator + (currentValue?.highest_unit_cost * currentValue?.quantity);
              }, 0) || 0;

              // Calculate expected material cost WITH markup
              const totals = ingredients.reduce(
                (acc, ing) => {
                  const qty = Number(ing?.quantity ?? 0);
                  const unitCost = Number(
                    ing?.highest_unit_cost ?? ing?.materialCost ?? 0
                  );
                  const markupPct = Number(ing?.markup ?? 0);

                  const lineSub = unitCost * qty;                 // Sub Total
                  const lineRetail = (lineSub * markupPct) / 100;    // Retail Amount
                  acc.subTotal += lineSub;
                  acc.retail += lineRetail;
                  acc.total += lineSub + lineRetail;           // Total = Sub + Retail
                  return acc;
                },
                { subTotal: 0, retail: 0, total: 0 }
              );

              const expectedMaterialCostWithMarkup = Number(totals.total.toFixed(2));

              // Calculate expected total service cost
              const expectedTotalServiceCost =
                Number(recipe.expected_total_service_cost) ||
                services.reduce((sum, s) => {
                  const amt = Number(s?.serviceAmount) || 0;
                  const qty = Number(s?.quantity) || 0;
                  return sum + amt * qty;
                }, 0);

                const expectedCost = expectedMaterialCost + Number(recipe.expected_service_cost || 0);

              // Update recipe with calculated costs
              const recipeUpdateData = {
                expected_cost: expectedCost,
                expected_material_cost: expectedMaterialCost,
                expected_material_cost_with_markup: expectedMaterialCostWithMarkup,
                expected_cost_with_markup: Number(expectedMaterialCostWithMarkup) + Number(expectedTotalServiceCost),
              };
              await Recipe.updateRecipe(recipeUpdateData, recipe.id);

              // Fetch and update all products using this recipe
              const productsForRecipe = await Product.fetchAllProductsByRecipe(true, false, recipe.id);

              await Promise.all(
                productsForRecipe.map(async (product) => {
                  const emc = Number(product.expected_material_cost || 0);
                  const ets = Number(product.expected_total_service_cost || 0);
                  const baseCost = emc + ets;

                  const wholesalePct = Number(product.wholesaleMarkup || 0);
                  // const retailPct = Number(product.retailMarkup || 0);

                  await Product.updateProduct(product.id, {
                    price: baseCost + (emc * wholesalePct) / 100,
                    // retail_price: baseCost + (emc * retailPct) / 100,
                    special_price: emc,
                    formula_cost: Number(expectedMaterialCostWithMarkup) + Number(expectedTotalServiceCost),
                  });
                })
              );

              //allRecipies[j].id \\  let allCatalog = await Product.catalogByReceipte(id);

              // const refreshedRecipe = await Recipe.getRecipeById(recipe.id);
              // const emcWithMarkup = Number(refreshedRecipe?.[0]?.expected_material_cost_with_markup) || 0;


              // const catalogProducts = await Product.catalogByReceipte(recipe.id);

              // for (const cat of catalogProducts) {

              //   const prevSpecial = Number(cat.special_price || 0);
              //   const prevFormula = Number(cat.formula_cost || 0);
              //   const service_cost = prevFormula - prevSpecial;

              //   const newSpecial = emcWithMarkup;

              //   const newFormula = service_cost + newSpecial;

              //   await Product.updateProduct(cat.id, {
              //     special_price: newSpecial,
              //     formula_cost: newFormula,
              //   });
              // }
              // for today !!
            })
          );

          // if (oldProductData[0]?.meterial == 1 && productData?.price !== oldProductData[0]?.price) {
          //   let allFinishedProducts = await Product.getFinishedProducts(id);
          //   for (let l = 0; l < allFinishedProducts.length; l++) {
          //     let updateData = {
          //       unit_cost: productData?.price,
          //       total_cost: productData?.price * allFinishedProducts[l]?.qty
          //     };
          //     await Product.updateFinishedProduct(allFinishedProducts[l]?.id, updateData);
          //   }
          // }

          let notificationMsg = oldProductData[0]?.meterial == 1
            ? `The material "${productData.name}" has been updated.`
            : `The product "${productData.name}" has been updated.`;
          let notificationUrl = "products";

          reply.send({ status: 1, message: `${replyMsg} "${productData?.name}" has been updated successfully!`, qbMsg: qbErr });

          // Prepare notification data
          const notificationData = {
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: productData.added_by,
          };

          // Get all admin IDs once
          const adminIds = await Admin.getAllAdminIds();
          
          // Build array of all users to notify (including the product owner)
          const usersToNotify = [productData.added_by, ...adminIds.filter(id => id != productData.added_by)];

          // Batch increment and add notifications
          await Promise.all(
            usersToNotify.map(async (userId) => {
              await Admin.incrementField(userId, "noti_count", 1);
              await Admin.addNotification({
                ...notificationData,
                user_id: userId,
              });
            })
          );
        } else {
          console.log("Failed to update the product");
          reply.send({ status: 0, message: `Failed to update ${replyMsg}` });
        }
      } catch (error) {
        console.log("Error updating the product: ", error);
        reply.send({ status: 3, message: `Error updating the ${replyMsg}` });
      }
    } else {
      console.log("The product already exists!");
      reply.send({ status: 2, message: `The ${replyMsg} already exists!` });
    }
  } catch (error) {
    console.log("Server Error: " + error);
    reply.send({ status: 500, message: "Server error" });
  }
};


const updateInhouse = async (request, reply) => {
  try {
    const { id, inhouse, isTrafficCatalog, origin_catalog } = request.body;
    const ids = Array.isArray(id) ? id.filter(Boolean) : [id];

    if (!ids.length || inhouse === undefined || isTrafficCatalog === undefined) {
      return reply.send({ status: 400, message: 'Missing required fields' });
    }

    const updatedIds = [];
    const failedIds = [];
    const notFoundIds = [];

    await Promise.all(
      ids.map(async (prodId) => {
        const productResult = await Product.getProductById(prodId);
        if (!productResult || productResult.length === 0) {
          notFoundIds.push(prodId);
          return;
        }

        const product = Array.isArray(productResult) ? productResult[0] : productResult;

        const updateData = {
          inhouse: parseInt(inhouse, 10),
          isTrafficCatalog: parseInt(isTrafficCatalog, 10),
          isNew: 1
        };

        // ✅ Set origin_catalog only if it's not already present
        if (!product.origin_catalog && origin_catalog) {
          updateData.origin_catalog = origin_catalog;
        }

        const result = await Product.updateInhouseCatalogStatus(prodId, updateData);
        if (result) {
          updatedIds.push(prodId);
        } else {
          failedIds.push(prodId);
        }
      })
    );

    if (!updatedIds.length) {
      return reply.send({ status: 0, message: 'Failed to update catalog', failedIds, notFoundIds });
    }

    return reply.send({
      status: 1,
      message: `${updatedIds.length} catalog${updatedIds.length > 1 ? 's' : ''} updated successfully`,
      updatedIds,
      failedIds,
      notFoundIds
    });
  } catch (error) {
    console.error('Error in updateInhouse:', error);
    return reply.send({ status: 500, message: 'Server error' });
  }
};

// Mark a catalog as viewed/read (clears new highlight)
const markProductViewed = async (request, reply) => {
  try {
    const { id } = request.body;
    if (!id) {
      return reply.send({ status: 400, message: 'Missing product id' });
    }

    const prodId = Array.isArray(id) ? id[0] : id;
    const updated = await Product.updateProduct(prodId, { isNew: 0 });

    if (updated) {
      return reply.send({ status: 1, message: 'Catalog marked as viewed', id: prodId });
    }
    return reply.send({ status: 0, message: 'Catalog not found' });
  } catch (error) {
    console.error('Error in markProductViewed:', error);
    return reply.send({ status: 500, message: 'Server error' });
  }
};

// Mark a labour cost service as viewed/read (clears new highlight)
const markLabourServiceViewed = async (request, reply) => {
  try {
    const { id } = request.body;
    if (!id) {
      return reply.send({ status: 400, message: 'Missing service id' });
    }

    const serviceId = Array.isArray(id) ? id[0] : id;
    const updated = await Product.updateLabourCostServices(serviceId, { isNew: 0 });

    if (updated) {
      return reply.send({ status: 1, message: 'Service marked as viewed', id: serviceId });
    }
    return reply.send({ status: 0, message: 'Service not found' });
  } catch (error) {
    console.error('Error in markLabourServiceViewed:', error);
    return reply.send({ status: 500, message: 'Server error' });
  }
};

///////////////////////////////quickbook delete fun
const deleteProductFromQuickBooks = async (productId, status) => {
  let token = await getNewAccessToken();

  const postDeleteProduct = async (useNewToken = true) => {
    if (useNewToken) {
      console.log('Refreshing access token...');
      token = await getNewAccessToken();
    }

    try {

      const getProductUrl = PRODUCTION
        ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item/${productId}`
        : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item/${productId}`;

      // Step 1: Retrieve the product by ID
      const getProductResponse = await axios.get(
        getProductUrl,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
            Accept: 'application/json',
          },
        }
      );


      if (getProductResponse.status === 200 && getProductResponse.data.Item) {
        const productData = getProductResponse.data.Item;
        console.log("productDataproductDataproductData", productData)
        console.log("productDataproductDataproductData status", status)

        // Step 2: Mark the product as inactive
        const updatedProductData = {
          ...productData,
          Id: productData.Id,
          Active: false || status == 1 ? true : false, // Set the Active flag to false to mark it as inactive
          SyncToken: productData.SyncToken
        };

        const DeleteProductUrl = PRODUCTION
          ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item?operation=update&minorversion=4`
          : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item?operation=update&minorversion=4`;

        token = await getNewAccessToken();
        // Step 3: Send the update request to deactivate the product
        const deleteProductResponse = await axios.post(
          DeleteProductUrl,
          updatedProductData,
          {
            headers: {
              Authorization: `Bearer ${token}`,
              'Content-Type': 'application/json',
              Accept: 'application/json',
            },
          }
        );

        if (deleteProductResponse.status === 200) {
          console.log("Product has been successfully marked as inactive in QuickBooks:", deleteProductResponse.data);
        } else {
          console.error("Failed to delete the product in QuickBooks:", deleteProductResponse.data);
        }
      } else {
        console.log(`Product with ID '${productId}' not found in QuickBooks.`);
      }
    } catch (error) {
      console.error("Error deleting product from QuickBooks:", error.response ? error.response.data : error.message);
      console.error("Validation error details:", error.response?.data?.Fault?.Error);

      const faultType = error.response?.data?.Fault?.type;
      if (faultType === 'AUTHENTICATION') {
        console.log('Access token expired, refreshing token...');
        try {
          // Retry the API call with a new access token
          await postDeleteProduct(true);
        } catch (refreshError) {
          console.error('Error refreshing token:', refreshError.response?.data || refreshError.message);
        }
      } else {
        console.error("Other error deleting product from QuickBooks:", error.response?.data || error.message);
      }
    }
  };

  await postDeleteProduct();
};

//////////////////////////////////
const deleteProduct = async (request, reply) => {
  try {
    let { id, status } = request.query;
    let currentDate = Math.round(moment().valueOf() / 1000);
    if (!empty(id)) {
      let productData = await Product.getProductById(id);


      /////////////////qb
      try {
        console.log('statussssssssssssssssss', status);

        if (productData[0]?.quickbookId) {
          await deleteProductFromQuickBooks(productData[0]?.quickbookId, status);
        }
      } catch (quickBooksError) {
        console.error("Error deleting product from QuickBooks:", quickBooksError);
      }
      ////////////////



      if (status == 2) {
        let deleted = await Product.deleteProduct(id);

        if (deleted) {
          // // Notification
          let notificationMsg = `The product "${productData[0].name}" has been deleted.`;
          let notificationUrl = "products";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};

          for (const adminId of adminIds) {

            const incrementResult = await Admin.incrementField(
              adminId,
              "noti_count",
              1
            );
            // console.log("incrementResult-------------", incrementResult);
            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: productData[0].added_by,
            };
            // console.log("notificationData------------", notificationData);
            const addNotificationResult = await Admin.addNotification(
              notificationData
            );
            // console.log("addNotificationResult-------", addNotificationResult);
          }
          reply.send({
            status: 1,
            message: `The product "${productData[0].name}" has been deleted successfully!`,
          });
        } else {
          reply.send({ status: 0, message: `Unable to delete the product!` });
        }
      } else if (status == 1) {
        // console.log("id------", id, "----status--------", status);
        const updatePayload = { status: status, isNew: 1 };
        let deleted = await Product.updateProduct(id, updatePayload);
        // console.log("deleted--1-------------", deleted);
        if (deleted) {
          // // Notification
          let notificationMsg = `The product "${productData[0].name}" has been made active.`;
          let notificationUrl = "products";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};
          //   console.log("adminIds====", adminIds);
          for (const adminId of adminIds) {
            // console.log("adminId----------", adminId);
            const incrementResult = await Admin.incrementField(
              adminId,
              "noti_count",
              1
            );
            // console.log("incrementResult-------------", incrementResult);
            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: productData[0].added_by,
            };
            // console.log("notificationData------------", notificationData);
            const addNotificationResult = await Admin.addNotification(
              notificationData
            );
            // console.log("addNotificationResult-------", addNotificationResult);
          }
          reply.send({
            status: 2,
            message: `The product "${productData[0].name}" has been made active!`,
          });
        } else {
          reply.send({ status: 3, message: "Unable to activate the product!" });
        }
      } else if (status == 0) {
        const updatePayload = { status: status, isNew: 1 };
        let deleted = await Product.updateProduct(id, updatePayload);
        // console.log("deleted--0-------------", deleted);
        if (deleted) {
          // Notification
          let notificationMsg = `The product "${productData[0].name}" has been made inactive.`;
          let notificationUrl = "products";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};
          //   console.log("adminIds====", adminIds);
          for (const adminId of adminIds) {
            // console.log("adminId----------", adminId);
            const incrementResult = await Admin.incrementField(
              adminId,
              "noti_count",
              1
            );
            // console.log("incrementResult-------------", incrementResult);
            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: productData[0].added_by,
            };
            // console.log("notificationData------------", notificationData);
            const addNotificationResult = await Admin.addNotification(
              notificationData
            );
            // console.log("addNotificationResult-------", addNotificationResult);
          }

          reply.send({
            status: 4,
            message: `The product "${productData[0].name}" has been made Inactive!`,
          });
        } else {
          reply.send({
            status: 5,
            message: "Unable to deactivate the product!",
          });
        }
      }
    } else {
      console.log("Error................");
      reply.send({ status: 500, message: "Error!" });
    }
  } catch (error) {
    console.log("error error",error)
    reply.status(500).send({ message: "Server error" });
  }
};

////////////////////////////////////////quickbook

let materialQbId = null;

// const addMaterialToQuickBooks = async (productData) => {
//   try {
//     console.log('Refreshing access token...');
//     const token = await getNewAccessToken();

//     const currentDate = moment().toISOString();
//     const asOfDate = moment().format('YYYY-MM-DD');

//         // Fetch dynamic account IDs based on account types
//         const assetAccounts = await getAccounts('Other Current Asset');   // Fetch asset accounts
//         const expenseAccounts = await getAccounts('Cost of Goods Sold'); // Fetch expense accounts

//         // Use the first account for each type (if available)
//         // const materialIncAccIdNew = incomeAccounts?.length > 0 ? incomeAccounts[0].Id : null;
//         const dynamicMaterialAssetAccId = assetAccounts?.length > 0 ? assetAccounts[0].Id : null;
//         const dynamicMaterialExpAccId = expenseAccounts?.length > 0 ? expenseAccounts[0].Id : null;

//     // QuickBooks product data structure
//     const quickBooksProductData = {
//       Name: productData.name,
//       Description: productData.description,
//       UnitPrice: productData.price,
//       Type: "Inventory",
//       IncomeAccountRef: {
//       value: materialIncAccId
//       },
//       AssetAccountRef: {
//         value:dynamicMaterialAssetAccId || materialAssetAccId
//       },
//       ExpenseAccountRef: {
//          value: dynamicMaterialExpAccId || materialExpAccId
//       },
//       TrackQtyOnHand: true,
//       QtyOnHand: productData.unit_qty,
//       InvStartDate: asOfDate,
//     };

//     console.log("QuickBooks Product Data:", JSON.stringify(quickBooksProductData, null, 2));

//     const apiUrl = PRODUCTION 
//     ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item` 
//     : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item`;

//     const response = await axios.post(
//       apiUrl,
//       quickBooksProductData,
//       {
//         headers: {
//           Authorization: `Bearer ${token}`,
//           'Content-Type': 'application/json',
//           Accept: 'application/json'
//         }
//       }
//     );

//     if (response.status === 200) {
//       materialQbId = response.data.Item.Id;
//       quickbookId = response.data.Item.Id;
//       console.log("Product has been successfully added to QuickBooks:", response.data);
//     } else {
//       console.error("Failed to add the product to QuickBooks:", response.data);
//       materialQbId=null
//     }
//   } catch (error) {
//     materialQbId=null
//     console.error("Error adding product to QuickBooks:", error.response ? error.response.data : error.message);
//     const faultType = error.response?.data?.Fault?.type;
//     if (faultType === 'ValidationFault') {
//       materialQbId=null
//       console.error("Validation error adding product to QuickBooks:", JSON.stringify(error.response?.data, null, 2));
//     } else {
//       materialQbId=null
//       console.error("Other error adding product to QuickBooks:", error.response?.data || error.message);
//     }
//   }
// };



////////////////////////////////////////



// let rel = findAccountByGlCode(4001)




// const findAccountByGlCode = async (glCode) => {
//   if (!glCode) return null; // Return null if no GL Code is provided

//   try {
//       const allAccounts = await getAllGLCodes(); // Fetch all accounts from QuickBooks

//       // ✅ Ensure comparison is done as a string
//       const matchedAccount = allAccounts.find(account => String(account.glCode) === String(glCode));

//       console.log('matchedAccount', matchedAccount, 'All Accounts:', allAccounts);

//       return matchedAccount ? matchedAccount.id : null; // Return ID if found, else null
//   } catch (error) {
//       console.error(`Error finding account for GL Code (${glCode}):`, error.message);
//       return null;
//   }
// };

// // ✅ Test with GL Code 4001
// findAccountByGlCode(4001).then(result => console.log('Matched Account ID:', result));

const addMaterialToQuickBooks = async (productData) => {
  try {
    // console.log('Refreshing access token...', productData);
    const token = await getNewAccessToken();

    const currentDate = moment().toISOString();
    const asOfDate = moment().format('YYYY-MM-DD');

    // Fetch dynamic account IDs based on account types
    // const assetAccounts = await getAccounts('Other Current Asset','Inventory');   // Fetch asset accounts
    const expenseAccounts = await getAccounts('Cost of Goods Sold', 'SuppliesMaterialsCogs');
    // const incomeAccounts = await getAccounts('Income', 'SalesOfProductIncome'); 
    const incomeAccounts = await getAccounts('Income', 'SalesOfProductIncome');
    //  console.log('incomeAccounts',incomeAccounts)

    //  const filteredIncomeAcc = incomeAccounts?.find(acc => 
    //   acc.FullyQualifiedName?.includes('Production Sales')
    // );

    const filteredIncomeAcc = incomeAccounts?.find(acc =>
      acc.FullyQualifiedName?.toLowerCase().includes('sales') ||
      acc.Name?.toLowerCase().includes('product') ||
      acc.AccountSubType === 'SalesOfProductIncome'
    );

    // Use the first account for each type (if available)
    // const materialIncAccIdNew = incomeAccounts?.length > 0 ? incomeAccounts[0].Id : null;
    const dynamicMaterialAssetAccId = await findAccountIdByAcctNum(1013200)       //assetAccounts?.length > 0 ? assetAccounts[0].Id : null;
    let dynamicMaterialExpAccId = expenseAccounts?.length > 0 ? expenseAccounts[0].Id : null;
    // let dynamicMaterialIncAccId = incomeAccounts?.length > 0 ? incomeAccounts[0].Id : null;
    let dynamicMaterialIncAccId = incomeAccounts?.length > 0 ? filteredIncomeAcc?.Id : null;

    if (productData?.incomeGlCode) {
      dynamicMaterialIncAccId = await findAccountByGlCode(productData?.incomeGlCode)
    }
    if (productData?.expenseGlCode) {
      dynamicMaterialExpAccId = await findAccountByGlCode(productData?.expenseGlCode)
    }

    console.log('dynamicMaterialAssetAccId', dynamicMaterialIncAccId)
    let itemTypeMap = {
      1: "Inventory",      // Inventory
      //  1: "NonInventory",      // Inventory
      2: "NonInventory",   // Non-Inventory
      3: "Service",        // Service
      4: "Group"           // Bundle (QuickBooks uses "Group" for bundles)
    };
    let qbType = itemTypeMap[productData.itemType] || "Inventory";

    if (!productData?.incomeGlCode) {
      dynamicMaterialIncAccId = await findAccountIdByAcctNum(4020000) //|| filteredIncomeAcc?.Id 
    }
    if (!productData?.expenseGlCode) {
      dynamicMaterialExpAccId = await findAccountIdByAcctNum(5041000) //|| expenseAccounts?.length > 0 ? expenseAccounts[0].Id : null;
    }

         let track=true
         if(qbType=="Inventory" || productData.itemType == 1){
          track=true
         }else{
          track=false
          
         }
         let productName = productData.name.replace(/[:\t\n\r]+/g, "-").trim();
         if (productName.length > 95) {
           productName = productName.substring(0, 95);
         }
    // QuickBooks product data structure
    let quickBooksProductData = {
      // Name: productData.name + locationInventoryCode,
      Name: productName + locationInventoryCode,
      Description: productData.description,
      UnitPrice: productData.price,
      PurchaseCost: productData.price,
      Type: qbType || "Inventory",
      Sku: productData.sku,
      ReorderPoint: productData.ReorderPoint,
      IncomeAccountRef: {
        value: dynamicMaterialIncAccId
      },
      AssetAccountRef: {
        value: dynamicMaterialAssetAccId
      },
      ExpenseAccountRef: {
        value: dynamicMaterialExpAccId
      },
      TrackQtyOnHand: track,
      QtyOnHand: productData.unit_qty || 0,
      InvStartDate: asOfDate,
    };

    console.log("datttttttttttttttttt", productData.qb_sub_category, productData.qb_category)
    // ✅ Handle Category & Subcategory Assignments Correctly
    if (productData.qb_sub_category && productData.qb_sub_category !== "null") {
      quickBooksProductData.SubItem = true;
      quickBooksProductData.ParentRef = { value: String(productData.qb_sub_category) };
    } else if (productData.qb_category && productData.qb_category !== "null") {
      quickBooksProductData.SubItem = true; // 🔹 Required when assigning a category
      quickBooksProductData.ParentRef = { value: String(productData.qb_category) };
    } else {
      // If no category or subcategory, do not set ParentRef or SubItem
      delete quickBooksProductData.SubItem;
      delete quickBooksProductData.ParentRef;
    }

    console.log("QuickBooks Product Data:", JSON.stringify(quickBooksProductData, null, 2));

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item`;

    const response = await axios.post(
      apiUrl,
      quickBooksProductData,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          Accept: 'application/json'
        }
      }
    );

    if (response.status === 200) {
      materialQbId = response.data.Item.Id;
      quickbookId = response.data.Item.Id;
      console.log("Product has been successfully added to QuickBooks:", response.data);
    } else {
      console.error("Failed to add the product to QuickBooks:", response.data);
      materialQbId = null
    }
  } catch (error) {
    materialQbId = null
    console.error("Error adding product to QuickBooks:", error.response ? error.response.data : error.message);
    const faultType = error.response?.data?.Fault?.type;
    if (faultType === 'ValidationFault') {
      materialQbId = null
      console.error("Validation error adding product to QuickBooks:", JSON.stringify(error.response?.data, null, 2));
    } else {
      materialQbId = null
      console.error("Other error adding product to QuickBooks:", error.response?.data || error.message);
    }
  }
};



const syncAllMaterialProductsToQuickBooks = async (req, res) => {
  try {
    const limit = 50
    const type = 2
    const productData = await Product.getAllCatalogMaterials(limit);
    console.log('productData', productData)
    if (productData.length == 0) {
      return res.send({ status: 1, message: "No material found to sync to QuickBooks" });
    }
    let failedProducts = [];
    let successProducts = [];
    for (const product of productData) {
      if (type == 1) {
        const productDataForQB = {
          name: product.name,
          description: product.description,
          price: product.price ? product.price : 0,
          unit_qty: product?.finishedProductQty,
          // qb_category:  product.qb_category,
          // qb_sub_category: product.qb_sub_category,
          sku: product.vpt_number,
          ReorderPoint: product.min_value,
          itemType: product?.material_types || 1,
          incomeGlCode: product.qb_income_account,
          expenseGlCode: product.qb_expense_account,
        };
        await addMaterialToQuickBooks(productDataForQB);
      } else {

        const updateData = {
          existingName: product.name,
          name: product?.name,
          description: product.description,
          price: product.price ? product.price : 0,
          unit_qty: product?.finishedProductQty,
          // qb_category:  product.qb_category,
          // qb_sub_category: product.qb_sub_category,
          sku: product.vpt_number,
          ReorderPoint: product.min_value,
          itemType: 1,
          incomeGlCode: null,
          expenseGlCode: null,
          quickbookId: product.quickbookId,
          finishedProductQty: product?.finishedProductQty,
        }
        await updateMaterialInQuickBooks(updateData);
      }
      if (quickbookId) {
        let productData = {}
        productData.syncWithQuickbooks = 1
        if (type == 1) {
          productData.quickbookId = quickbookId
        }
        successProducts.push(product.name);
        let result = await Product.updateProduct(product.id, productData);
        if (result) {
          console.log('Product synced to QuickBooks : ', product.name);
        } else {
          console.log('Failed to sync product to QuickBooks : ', product.name);
        }
      } else {
        failedProducts.push(product.name);
      }
    }
    res.send({ status: 1, message: "Products synced to QuickBooks successfully", failedProducts: failedProducts, successProducts });
  } catch (error) {
    console.log('syncAllCatalogProductsToQuickBooks error', error);
    res.send({ status: 0, message: "Failed to sync products to QuickBooks", error: error });
  }
}
// (()=>{
//   syncAllMaterialProductsToQuickBooks()
// })()

const addMaterialProduct = async (request, reply) => {
  try {
    // console.log("productData---------", request.body);
    //  return
    let currentDate = Math.round(moment().valueOf() / 1000);
    let productData = {};
    
    productData.name = request.body.name.value;
    const formattedDescription = (request.body.description.value || '').replace(/\r?\n/g, '\n');
    productData.description = formattedDescription;
    productData.description_lines = formattedDescription ? formattedDescription.split('\n') : [];
    if (request.body.vendor?.value) {
      // Ensure the value is split into an array and then joined back as a string
      productData.vendor_id = request.body.vendor.value         // Join it back to ensure it's a comma-separated string
    } else {
      productData.vendor_id = '';
    }
    if (request.body.vpt_number) {
      productData.vpt_number = request.body.vpt_number.value;
    }
    // Secondary vendor handling
    if (request.body.secondary_vendor?.value) {
      productData.secondary_vendor_id = request.body.secondary_vendor.value;
    } else {
      productData.secondary_vendor_id = '';
    }
    // Secondary SKU handling
    if (request.body.secondary_sku) {
      productData.secondary_sku = request.body.secondary_sku.value;
    }
    productData.price = request.body.price.value;
    productData.special_price = request.body.price.value;
    productData.unit = request.body.unit?.value
    productData.material_types = request.body.material_types?.value
    productData.min_value = parseInt(request.body?.min_value?.value);
    productData.max_value = parseInt(request.body?.max_value?.value);
    productData.unit_qty = parseInt(request.body?.unit_qty?.value)

    productData.tax_category = request.body.tax.value || 0;
    productData.timestamp = currentDate;
    productData.added_by = request.body.added_by.value;
    productData.tags = (request.body.tags?.value != '') ? request.body.tags?.value : null
    productData.meterial = 1
    productData.status = 1;
    materialQbId = null
    productData.inhouse = request.body.inhouse.value || 1;
    productData.retail_price = request.body.markup.value;
    productData.qb_category = (request?.body?.qb_category?.value && request?.body?.qb_category?.value != 'null') ? request?.body?.qb_category?.value : null;
    productData.qb_sub_category = (request?.body?.qb_sub_category?.value && request?.body?.qb_sub_category?.value != 'null') ? request?.body?.qb_sub_category?.value : null;
    productData.account_type = request.body.account_type.value || '';
    productData.taxable = request.body.taxable.value || 1;

    productData.category_id = parseInt(request.body.category.value) || null;
    productData.sub_category_id = parseInt(request.body.sub_category.value) || null;
    productData.taxCodeId = parseToNumberOrNull(request.body?.taxCodeId?.value) || null
    productData.taxCode = request.body?.taxCode?.value || null
    productData.bin_location = request.body?.bin_location?.value || null
    productData.isNew = 1;

    const normalizeValue = (val) => {
      return (val && val !== 'null' && val !== 'undefined') ? val : null;
    };

    // productData.qb_income_account = (request?.body?.income_account?.value && request?.body?.income_account?.value != 'null') ? request?.body?.income_account?.value : null;
    // productData.qb_expense_account= (request?.body?.expense_account?.value && request?.body?.expense_account?.value != 'null') ? request?.body?.expense_account?.value : null;

    productData.qb_income_account = normalizeValue(request?.body?.income_account?.value);
    productData.qb_expense_account = normalizeValue(request?.body?.expense_account?.value);

    const sampleProductData = {
      name: productData.name,
      description: productData.description,
      price: productData.price ? productData.price : 0,
      unit_qty: 0,
      qb_category: productData.qb_category,
      qb_sub_category: productData.qb_sub_category,
      sku: productData.vpt_number,
      ReorderPoint: productData.min_value,
      itemType: productData?.material_types || 1,
      incomeGlCode: productData.qb_income_account,
      expenseGlCode: productData.qb_expense_account,
    };

    try {

      await addMaterialToQuickBooks(sampleProductData)
    } catch (error) {
      console.log(error, "error")
    }

    if (materialQbId) {
      productData.syncWithQuickbooks = 1,
        productData.quickbookId = materialQbId
    }

    const [productNameExist, isServiceNameExist] = await Promise.all([
      Product.isMaterialNameExist(productData.name),
      Product.isServiceNameExist(productData.name)
    ]);
    // console.log("productNameExist : ", productNameExist);
    if (productNameExist != true && isServiceNameExist != true) {
      try {
        // Handle thumbnail URL from Supabase
        const thumbnailUrl = request.body?.thumbnail?.value || request.body?.thumbnail;
        if (thumbnailUrl) {
          productData.thumbnail = thumbnailUrl;
          // console.log("Material Thumbnail URL set:", thumbnailUrl);
        }

        let result = await Product.addProduct(productData);
        if (result) {
          reply.send({ status: 1, message: " Material label has been successfully added!." });

          // Notification
          let notificationMsg = `New Material label "${productData.name}" has been added.`;
          let notificationUrl = "products";

          // Alert to user of new added product

          const incrementResult_User = await Admin.incrementField(productData.added_by, "noti_count", 1);

          let notificationData_user = {
            user_id: productData.added_by,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: productData.added_by,
          };

          const addNotificationResult_user = await Admin.addNotification(notificationData_user);

          // Alert to all admins of new added product

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};
          for (const adminId of adminIds) {
            if (adminId != productData.added_by) {
              const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
              notificationData = {
                user_id: adminId,
                url: notificationUrl,
                timestamp: currentDate,
                message: notificationMsg,
                status: 1,
                from_id: productData.added_by,
              };
              const addNotificationResult = await Admin.addNotification(notificationData);
            }
          }
        } else {
          console.log("Failed to add the material");
          reply.send({ status: 0, message: "Failed to add the material" });
        }
      } catch (error) {
        console.log("Failed to add the product : ", error);
        reply.send({ status: 0, message: "Failed to add the material" });
      }
    } else {
      console.log("The material already exist!");
      reply.send({ status: 2, message: "The material already exist!" });
    }
  } catch (error) {
    console.log("Server error", error);
    reply.status(500).send({ message: "Server error" });
  }
};



// const uploadMaterial = async (request, reply) => {
//   try {
//     materialQbId=null;
//     let currentDate = Math.round(moment().valueOf() / 1000);
//     let { data, userId } = request.body
//     console.log("############Data", data);
//     let productData = {}
//     let units = [{ id: 1, name: 'Square feet' },
//     { id: 2, name: 'Lineal feet' },
//     { id: 3, name: 'Square Inches' },
//     { id: 4, name: 'Unit' }]
//     let count=0;
//     for (let i = 0; i < data.length; i++) {
//       let qty=false;
//       productData.name = data[i].name;
//       productData.description = data[i].description;
//       productData.price = data[i].expectedCost;
//       productData.special_price = data[i].expectedCost;
//       console.log("###########Unitdata1",(data[i].unit.trim()).toLowerCase());


//       let unit = units.find(u => u.name.toLowerCase() === (data[i].unit.trim()).toLowerCase());
//       if (unit) {
//           productData.unit = unit.id;
//       } else {
//           productData.unit = ""; // if unit not found in the units array, use the original unit value
//       }
//       productData.min_value = data[i].minimumValue;
//       productData.max_value = data[i].maximumValue
//       if(productData.unit==4){
//         productData.unit_qty = 0
//         qty=true
//       }
//       else{
//         if(!empty(data[i].numberOfUnit)){
//           if (Number.isInteger(data[i].numberOfUnit)) {
//             productData.unit_qty = data[i].numberOfUnit;
//             qty = true;
//           }
//         }

//       }

//       // productData.category_id =parseInt(request.body.category.value);
//       // productData.sub_category_id = parseInt(request.body.sub_category.value);
//       productData.tax_category = 0;
//       productData.timestamp = currentDate;
//       productData.added_by = userId || 1;
//       productData.tags = data[i].tags
//       productData.meterial = 1
//       productData.status = 1;
//       let categoryId=await Product.checkMaterialCategoryExist(data[i].category.trim())
//       productData.category_id =categoryId
//       console.log("###############categoryId", categoryId);
//       if(categoryId){
//         let subCategoryId=await Product.checkMaterialSubCategoryExist(data[i].subCategory.trim(),categoryId)
//         productData.sub_category_id=subCategoryId
//       }
//       let productNameExist = await Product.isMaterialNameExist(productData.name);
//       let isServiceNameExist = await Product.isServiceNameExist(productData.name);
//       console.log("##########product",productData);

//       const sampleProductData = {
//         name: productData.name,
//         description: productData.description,
//         price: productData.price?productData.price :0,
//         unit_qty: 0,
//       };


//       if(!empty(categoryId)&&!empty(productData.unit)&&(productNameExist != true)&&(isServiceNameExist != true)&&!empty(productData.description)&&!empty(productData.price)&&!empty(productData.min_value)&&!empty(productData.max_value)&&!empty(productData.name)&&qty&&!empty(productData.sub_category_id)){

//         try {

//           await addMaterialToQuickBooks(sampleProductData)
//         } catch (error) {
//           console.log(error,"error")
//         }

//         if(materialQbId){
//           productData.syncWithQuickbooks=1,
//           productData.quickbookId=materialQbId
//         }

//         let result = await Product.addProduct(productData);

//         if(result){
//           count++
//         }
//       }

//     }
//     let ignored=data.length-count
//     if (count>0) {
//       reply.send({ status: 1, message:  count+" rows effected, "+ignored+" ignored " });

//       // Notification
//       let notificationMsg = `New Material uploaded ! `+count+" rows effected, "+ignored+" ignored ";
//       let notificationUrl = "products";

//       // Alert to user of new added product

//       const incrementResult_User = await Admin.incrementField(productData.added_by, "noti_count", 1);

//       let notificationData_user = {
//         user_id: productData.added_by,
//         url: notificationUrl,
//         timestamp: currentDate,
//         message: notificationMsg,
//         status: 1,
//         from_id: productData.added_by,
//       };

//       const addNotificationResult_user = await Admin.addNotification(notificationData_user);

//       // Alert to all admins of new added product

//       let adminIds = await Admin.getAllAdminIds();
//       let notificationData = {};
//       for (const adminId of adminIds) {
//         if (adminId != productData.added_by) {
//           const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
//           notificationData = {
//             user_id: adminId,
//             url: notificationUrl,
//             timestamp: currentDate,
//             message: notificationMsg,
//             status: 1,
//             from_id: productData.added_by,
//           };
//           const addNotificationResult = await Admin.addNotification(notificationData);
//         }
//       }
//     } else {
//       console.log("Failed to upload the material");
//       reply.send({ status: 0, message: "Failed to upload the material, Please check the category, and sub category exist and material name doesnot already exists" });
//     }

//     console.log("###############final ", count+"rows effected");

//   }
//   catch (error) {
//     console.log("###############Error", error);
//     reply.send({ status: 0, message: "Failed to upload the material",error });
//   }
// };




const getQuickbookCategoryId = async (name) => {
  try {
    // Get the access token
    let token = await getNewAccessToken();

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

    // Search for the product by name
    const searchResponse = await axios.get(
      `${apiUrl}/query?query=select * from Item where Name='${name + locationInventoryCode}'&minorversion=63`,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          Accept: 'application/json',
        },
      }
    );

    const product = searchResponse.data.QueryResponse.Item?.[0];

    if (!product) {
      console.error(`Product with name "${name}" not found in QuickBooks.`);
      return null;
    }
    console.log("Product found:", product);
    return product.Id;
  } catch (error) {
    console.log('getQuickbookCategoryId error', error);
    return null
  }
}





const uploadMaterial = async (request, reply) => {
  try {
    materialQbId = null;
    let currentDate = Math.round(moment().valueOf() / 1000);
    let { data, userId, materialTab } = request.body;
    console.log("############Data", data);
    let productData = {};
    // let units = [
    //   { id: 1, name: 'Square feet', short_name: 'Sq ft', unit_id: 1 },
    //   { id: 2, name: 'Linear feet', short_name: 'Lin ft', unit_id: 1 },
    //   { id: 3, name: 'Square inches', short_name: 'Sq in', unit_id: 1 },
    //   { id: 4, name: 'Unit', short_name: 'Unit', unit_id: 2 },
    //   { id: 5, name: 'Gallons', short_name: 'Gal', unit_id: 3 },
    //   { id: 6, name: 'Quarts', short_name: 'Qt', unit_id: 3 },
    //   { id: 7, name: 'Milliliter', short_name: 'ml', unit_id: 3 },
    //   { id: 8, name: 'Ounce', short_name: 'oz', unit_id: 3 },
    //   { id: 9, name: 'Kilogram', short_name: 'kg', unit_id: 4 },
    //   { id: 10, name: 'Gram', short_name: 'g', unit_id: 4 },
    //   { id: 11, name: 'Milligram', short_name: 'mg', unit_id: 4 },
    // ];
    const units = [
      { id: 1, name: 'Square feet', short_name: 'Sq ft', unit_id: 1 },
      { id: 2, name: 'Linear feet', short_name: 'Lin ft', unit_id: 1 },
      { id: 3, name: 'Square inches', short_name: 'Sq in', unit_id: 1 },
      { id: 4, name: 'Unit', short_name: 'Unit', unit_id: 2 },
      { id: 5, name: 'Gallons', short_name: 'Gal', unit_id: 3 },
      { id: 6, name: 'Quarts', short_name: 'Qt', unit_id: 3 },
      { id: 7, name: 'Milliliter', short_name: 'ml', unit_id: 3 },
      { id: 8, name: 'Liter', short_name: 'L', unit_id: 3 },
      { id: 9, name: 'Pint', short_name: 'pt', unit_id: 3 },
      { id: 10, name: 'Half Pint', short_name: 'half-pt', unit_id: 3 },
      { id: 11, name: 'Ounce', short_name: 'oz', unit_id: 3 },
      { id: 12, name: 'Kilogram', short_name: 'kg', unit_id: 4 },
      { id: 13, name: 'Gram', short_name: 'g', unit_id: 4 },
      { id: 14, name: 'Milligram', short_name: 'mg', unit_id: 4 },
      { id: 15, name: 'Each', short_name: 'EA', unit_id: 2 },

    ];
    const MATERIALTYPES = [
      // { id: 1, name: 'Inventory', },
      { id: 2, name: 'Non-Inventory', },
      { id: 3, name: 'Services', },
      // { id: 4, name: 'Buldle', },
    ]
    let count = 0;

    for (let i = 0; i < data.length; i++) {
      let qty = false;
      productData.name = data[i].name;
      productData.description = data[i].description;
      productData.price = data[i].expectedCost;
      productData.special_price = data[i].expectedCost;
      productData.qb_income_account = data[i].IncomeAccount;
      productData.qb_expense_account = data[i].ExpenseAccount;

      let unit = units.find(u => u.name.toLowerCase() === (data[i].unit.trim()).toLowerCase());
      let materialType = MATERIALTYPES.find(u => u.name.toLowerCase() === (data[i].Item_type.trim()).toLowerCase());
      productData.unit = unit ? unit.id : "";
      productData.material_types = materialType ? materialType.id : "";

      // let Account_type = data[i].Account_type.toLowerCase().includes("income") ? 1 : 2;

      let Account_type = [];
      if (data[i].Account_type.toLowerCase().includes("income")) {
        Account_type.push(1);
      }
      if (data[i].Account_type.toLowerCase().includes("expense")) {
        Account_type.push(2);
      }

      productData.account_type = Account_type?.length > 0 ? Account_type.join(',') : '';
      productData.min_value = data[i].minimumValue;
      productData.max_value = data[i].maximumValue;
      productData.inhouse = materialTab == 'Inhouse' ? 1 : 0;
      if (productData.unit == 4) {
        productData.unit_qty = 0;
        qty = true;
      } else if (!empty(data[i].numberOfUnit) && Number.isInteger(data[i].numberOfUnit)) {
        productData.unit_qty = data[i].numberOfUnit;
        qty = true;
      }

      // Find vendor_id by vendor name
      let vendorNames = data[i].Vendor ? data[i].Vendor.split(',') : [];
      let vendorIds = [];

      for (let vendorName of vendorNames) {
        let vendor = await Client.findVendorByName(vendorName.trim());
        if (vendor) {
          vendorIds.push(vendor);
        }
      }

      console.log("vendorvendorvendorvendor", vendorIds)
      // Save vendor_ids as a comma-separated string
      productData.vendor_id = vendorIds.join(',');
      productData.vpt_number = data[i].SKU || "";

      // Find secondary_vendor_id by secondary vendor name
      let secondaryVendorNames = data[i].SecondaryVendor ? data[i].SecondaryVendor.split(',') : [];
      let secondaryVendorIds = [];

      for (let vendorName of secondaryVendorNames) {
        let vendor = await Client.findVendorByName(vendorName.trim());
        if (vendor) {
          secondaryVendorIds.push(vendor);
        }
      }

      console.log("secondaryVendorIds", secondaryVendorIds)
      // Save secondary_vendor_ids as a comma-separated string
      productData.secondary_vendor_id = secondaryVendorIds.join(',');
      productData.secondary_sku = data[i].SecondarySKU || "";

      productData.tax_category = 0;
      productData.timestamp = currentDate;
      productData.added_by = userId || 1;
      productData.tags = data[i].tags;
      productData.meterial = 1;
      productData.status = 1;

      let categoryId = await Product.checkMaterialCategoryExist(data[i].category.trim());
      productData.category_id = categoryId;
      let categoryQuickbookId = await Product.checkMaterialQbCategoryExist(data[i].category.trim())
      console.log('categoryQuickbookId 1', categoryQuickbookId)
      if (!categoryQuickbookId) {
        categoryQuickbookId = await getQuickbookCategoryId(data[i].category.trim())

      }
      console.log('categoryId---', categoryId)
      console.log('categoryQuickbookId---', categoryQuickbookId)
      if (categoryId) {
        productData.qb_category = categoryQuickbookId ? Number(categoryQuickbookId) : null;
        let subCategoryId = await Product.checkMaterialSubCategoryExist(data[i].subCategory.trim(), categoryId);
        productData.sub_category_id = subCategoryId;

        let subCategoryQuickbookId = await Product.checkMaterialQbSubCategoryExist(data[i].subCategory.trim(), categoryId)
        console.log('subCategoryQuickbookId 1', subCategoryQuickbookId)

        if (!subCategoryQuickbookId) {
          subCategoryQuickbookId = await getQuickbookCategoryId(data[i].subCategory.trim(), categoryId)
        }
        productData.qb_sub_category = subCategoryQuickbookId ? Number(subCategoryQuickbookId) : null;

        let subCatData = await Product.checkCatalogSubCategoryExist(subCategoryId)
        productData.retail_price = subCatData.markup
      } else if (categoryQuickbookId) {
        productData.category_id = null;
        productData.qb_category = Number(categoryQuickbookId);

        let subCategoryQuickbookId = await Product.checkMaterialQbSubCategoryExist(data[i].subCategory.trim(), categoryId)
        console.log('subCategoryQuickbookId 2', subCategoryQuickbookId)

        if (!subCategoryQuickbookId) {
          subCategoryQuickbookId = await getQuickbookCategoryId(data[i].subCategory.trim())
        }
        productData.sub_category_id = null;
        productData.qb_sub_category = Number(subCategoryQuickbookId);
        productData.retail_price = 0
      }

      let productNameExist = await Product.isMaterialNameExist(productData.name);
      let isServiceNameExist = await Product.isServiceNameExist(productData.name);

      if (
        (!empty(categoryId) ||
          !empty(categoryQuickbookId)) &&
        !empty(productData.unit) &&
        !productNameExist &&
        !isServiceNameExist &&
        !empty(productData.description) &&
        // !empty(productData.price) &&
        !empty(productData.min_value) &&
        !empty(productData.max_value) &&
        !empty(productData.name) &&
        qty &&
        (!empty(productData.sub_category_id) ||
          !empty(productData.qb_sub_category))
      ) {
        try {
          await addMaterialToQuickBooks({
            name: productData.name,
            description: productData.description,
            price: productData.price || 0,
            sku: productData.vpt_number,

            qb_category: productData.qb_category,
            qb_sub_category: productData.qb_sub_category,

            ReorderPoint: productData.min_value || 0,
            unit_qty: 0,
            itemType: productData?.material_types || 1,
            incomeGlCode: productData.qb_income_account,
            expenseGlCode: productData.qb_expense_account,
          });
        } catch (error) {
          console.log(error, "error");
        }

        if (materialQbId) {
          productData.syncWithQuickbooks = 1;
          productData.quickbookId = materialQbId;
        }

        // Handle thumbnail URL from Supabase (if provided in data)
        if (data[i].thumbnail) {
          productData.thumbnail = data[i].thumbnail;
          console.log("Upload Material Thumbnail URL set:", data[i].thumbnail);
        }

        let result = await Product.addProduct(productData);
        if (result) {
          count++;
        }
      }
    }

    console.log('productData:', productData);
    let ignored = data.length - count;
    if (count > 0) {
      reply.send({ status: 1, message: count + " rows affected, " + ignored + " ignored" });
    } else {
      console.log("Failed to upload the material");
      reply.send({ status: 0, message: "Failed to upload the material. Please check the category, sub-category exists, and the material name does not already exist." });
    }

    console.log("###############final ", count + " rows affected");
  } catch (error) {
    console.log("###############Error", error);
    reply.send({ status: 0, message: "Failed to upload the material", error });
  }
};


// const uploadCatalog = async (request, reply) => {
//   try {
//     let currentDate = Math.round(moment().valueOf() / 1000);
//     let { data, userId } = request.body
//     console.log("############Data", data);
//     let productData = {}
//     // let units = [
//     //   { id: 1, name: 'Square feet', short_name: 'Sq ft', unit_id: 1 },
//     //   { id: 2, name: 'Linear feet', short_name: 'Lin ft', unit_id: 1 },
//     //   { id: 3, name: 'Square inches', short_name: 'Sq in', unit_id: 1 },
//     //   { id: 4, name: 'Unit', short_name: 'Unit', unit_id: 2 },
//     //   { id: 5, name: 'Gallons', short_name: 'Gal', unit_id: 3 },
//     //   { id: 6, name: 'Quarts', short_name: 'Qt', unit_id: 3 },
//     //   { id: 7, name: 'Milliliter', short_name: 'ml', unit_id: 3 },
//     //   { id: 8, name: 'Ounce', short_name: 'oz', unit_id: 3 },
//     //   { id: 9, name: 'Kilogram', short_name: 'kg', unit_id: 4 },
//     //   { id: 10, name: 'Gram', short_name: 'g', unit_id: 4 },
//     //   { id: 11, name: 'Milligram', short_name: 'mg', unit_id: 4 },
//     // ]

//     const units = [
//       { id: 1, name: 'Square feet', short_name: 'Sq ft', unit_id: 1 },
//       { id: 2, name: 'Linear feet', short_name: 'Lin ft', unit_id: 1 },
//       { id: 3, name: 'Square inches', short_name: 'Sq in', unit_id: 1 },
//       { id: 4, name: 'Unit', short_name: 'Unit', unit_id: 2 },
//       { id: 5, name: 'Gallons', short_name: 'Gal', unit_id: 3 },
//       { id: 6, name: 'Quarts', short_name: 'Qt', unit_id: 3 },
//       { id: 7, name: 'Milliliter', short_name: 'ml', unit_id: 3 },
//       { id: 8, name: 'Liter', short_name: 'L', unit_id: 3 },
//       { id: 9, name: 'Pint', short_name: 'pt', unit_id: 3 },
//       { id: 10, name: 'Half Pint', short_name: 'half-pt', unit_id: 3 },
//       { id: 11, name: 'Ounce', short_name: 'oz', unit_id: 3 },
//       { id: 12, name: 'Kilogram', short_name: 'kg', unit_id: 4 },
//       { id: 13, name: 'Gram', short_name: 'g', unit_id: 4 },
//       { id: 14, name: 'Milligram', short_name: 'mg', unit_id: 4 },
//       { id: 15, name: 'Each', short_name: 'EA', unit_id: 2 },

//   ];

//     let count=0;
//     for (let i = 0; i < data.length; i++) {
//       let qty=true;
//       productData.name = data[i].name;
//       productData.description = data[i].description;
//       productData.vpt_number = data[i].SKU || "";


//       console.log("###########Unitdata1",(data[i].unit?.trim())?.toLowerCase());


//       let unit = units.find(u => u.name.toLowerCase() === (data[i]?.unit?.trim())?.toLowerCase());
//       console.log("###########unit",unit);
//       if (unit) {
//           productData.unit = unit.id;
//       } else {
//           productData.unit = ""; // if unit not found in the units array, use the original unit value
//       }
//       // productData.min_value = data[i].minimumValue;
//       // productData.max_value = data[i].maximumValue
//       if(productData.unit===4){
//         productData.unit_qty = 0
//         qty=true
//       }
//       else{
//         productData.unit_qty =1
//         // if(!empty(data[i].numberOfUnit)){
//         //   if (Number.isInteger(data[i].numberOfUnit)) {
//         //     productData.unit_qty = data[i].numberOfUnit;
//         //     qty = true;
//         //   }
//         // }

//       }

//       let workflow=await Product.checkWorkflowExist(data[i].workflow.trim())
//       if(workflow){
//         productData.workflow =workflow

//       }
//       console.log("##########workflow",workflow);
//       let labourPrice=0
//       let recipe=await Product.checkRecipeExist(data[i].productFormula.trim())
//       console.log("##########TTTTT",recipe);
//       if(recipe){

//         productData.recipe_id=recipe.id
//         productData.special_price=recipe.expected_material_cost
//         labourPrice=recipe.expected_total_service_cost
//         // productData.pf_retail_markup =recipe.total_retail_markup
//       }
//       console.log("##########labourPrice",labourPrice);
//       productData.price = data[i].cost;
//       // productData.category_id =parseInt(request.body.category.value);
//       // productData.sub_category_id = parseInt(request.body.sub_category.value);
//       productData.tax_category = 0;
//       productData.timestamp = currentDate;
//       productData.added_by = userId || 1;
//       productData.tags = data[i].tags

//       productData.status = 1;
//       let categoryId=await Product.checkCatalogCategoryExist(data[i].category.trim())
//       if(categoryId&&recipe){
//         productData.category_id =categoryId.id
//         // productData.retail_price= labourPrice+productData.special_price+(categoryId.markup*productData.special_price/100)
//         // productData.price = labourPrice+productData.special_price+(categoryId.wholesale_markup*productData.special_price/100)

//       }

//       console.log("###############categoryId", categoryId);
//       if(categoryId){
//         let subCategoryId=await Product.checkMaterialSubCategoryExist(data[i].subCategory.trim(),categoryId.id)
//         productData.sub_category_id=subCategoryId

//         let subCatData = await Product.checkCatalogSubCategoryExist(subCategoryId)
//         console.log('subCategoryIdsubCategoryId',subCategoryId,subCatData)

//         productData.retail_price= labourPrice+productData.special_price+(subCatData.markup*productData.special_price/100)
//         productData.price = labourPrice+productData.special_price+(subCatData.wholesale_markup*productData.special_price/100)
//       }
//       let productNameExist = await Product.isProductNameExist(productData.name);
//       let isServiceNameExist = await Product.isServiceNameExist(productData.name);

//       console.log("###############productNameExist",productNameExist)
//       console.log("###############  productData.retail_price",  productData.retail_price)
//       console.log("###############productData.price",productData.price)

//       console.log('dataaaaaaaaaaaaaaa',productData);

//       const sampleProductData = {
//         name: productData.name,
//         unit_qty: productData.unit_qty?productData.unit_qty:0,
//         cost: productData.special_price ? productData.special_price : 0 ,
//         wholeSalePrice:productData?.price || 0,
//         retailPrice:productData?.retail_price || 0,
//         sku: productData.vpt_number,
//       };
//       quickbookId=null


//       if(!empty(categoryId) && !empty(productData.unit) && !empty(workflow)&&(productNameExist != true)&&(isServiceNameExist != true)&&!empty(productData.description)&&!empty(productData.price)&&!empty(recipe)&& qty &&!empty(productData.sub_category_id)){

//         try {

//           productResponse = await addProductToQuickBooks(sampleProductData);
//        } catch (error) {
//          console.log('err',error);

//        }

//        if(quickbookId){
//          productData.syncWithQuickbooks=1,
//          productData.quickbookId=quickbookId

//        }

//         let result = await Product.addProduct(productData);
//         console.log("###############result",result);
//         if(result){
//           count++
//         }
//       }

//     }
//     let ignored=data.length-count
//     if (count>0) {
//       reply.send({ status: 1, message:  count+" rows effected, "+ignored+" ignored " });

//       // Notification
//       let notificationMsg = `New Catalog uploaded ! `+count+" rows effected, "+ignored+" ignored ";
//       let notificationUrl = "products";

//       // Alert to user of new added product

//       const incrementResult_User = await Admin.incrementField(productData.added_by, "noti_count", 1);

//       let notificationData_user = {
//         user_id: productData.added_by,
//         url: notificationUrl,
//         timestamp: currentDate,
//         message: notificationMsg,
//         status: 1,
//         from_id: productData.added_by,
//       };

//       const addNotificationResult_user = await Admin.addNotification(notificationData_user);

//       // Alert to all admins of new added product

//       let adminIds = await Admin.getAllAdminIds();
//       let notificationData = {};
//       for (const adminId of adminIds) {
//         if (adminId != productData.added_by) {
//           const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
//           notificationData = {
//             user_id: adminId,
//             url: notificationUrl,
//             timestamp: currentDate,
//             message: notificationMsg,
//             status: 1,
//             from_id: productData.added_by,
//           };
//           const addNotificationResult = await Admin.addNotification(notificationData);
//         }
//       }
//     } else {
//       console.log("Failed to upload the catalog");
//       reply.send({ status: 0, message: "Failed to upload the catalog. Please check the category, sub category, workflow, product formula exist and product name doesnot already exists" });
//     }

//     console.log("###############final ", count+"rows effected");

//   }
//   catch (error) {
//     console.log("###############Error", error);
//     reply.send({ status: 0, message: "Failed to upload the catalog",error });
//   }
// };

const AVALARA_TAX_MAP = [
  {
    "id": 322774556,
    "companyId": 8567346,
    "itemCode": "1002",
    "taxCodeId": 8087,
    "taxCode": "P0000000",
    "description": "Tangible Goods",
    "createdDate": "2025-05-29T20:16:45.257",
    "createdUserId": 6732394,
    "modifiedDate": "2025-05-29T20:16:45.257",
    "modifiedUserId": 6732394,
    "itemStatus": [
      {
        "Status": "Complete",
        "ModifiedDate": "2025-05-29T20:16:45.41"
      }
    ]
  },
  {
    "id": 322775001,
    "companyId": 8567346,
    "itemCode": "1000",
    "taxCodeId": 8703,
    "taxCode": "SI020100",
    "description": "Sign Installation - Temporary Signage",
    "createdDate": "2025-05-29T20:37:27.14",
    "createdUserId": 6732394,
    "modifiedDate": "2025-05-29T20:49:41.02",
    "modifiedUserId": 6732394,
    "itemStatus": [
      {
        "Status": "Complete",
        "ModifiedDate": "2025-05-29T20:49:41.14"
      }
    ]
  },
  {
    "id": 322775134,
    "companyId": 8567346,
    "itemCode": "1001",
    "taxCodeId": 37240,
    "taxCode": "SD086570",
    "description": "Graphic Design Services",
    "createdDate": "2025-05-29T20:43:34.347",
    "createdUserId": 6732394,
    "modifiedDate": "2025-05-29T20:43:34.347",
    "modifiedUserId": 6732394,
    "itemStatus": [
      {
        "Status": "Complete",
        "ModifiedDate": "2025-05-29T20:43:34.593"
      }
    ]
  },
  {
    "id": 322775146,
    "companyId": 8567346,
    "itemCode": "1003",
    "taxCodeId": 625004,
    "taxCode": "OF080001",
    "description": "Permit Fees",
    "createdDate": "2025-05-29T20:45:27.55",
    "createdUserId": 6732394,
    "modifiedDate": "2025-05-29T20:45:27.55",
    "modifiedUserId": 6732394,
    "itemStatus": [
      {
        "Status": "Complete",
        "ModifiedDate": "2025-05-29T20:45:27.717"
      }
    ]
  },
  {
    "id": 322775156,
    "companyId": 8567346,
    "itemCode": "1004",
    "taxCodeId": 88138,
    "taxCode": "SD086571",
    "description": "Signage Production - Banners, Nonelectric",
    "createdDate": "2025-05-29T20:47:41.75",
    "createdUserId": 6732394,
    "modifiedDate": "2025-05-29T20:47:41.75",
    "modifiedUserId": 6732394,
    "itemStatus": [
      {
        "Status": "Complete",
        "ModifiedDate": "2025-05-29T20:47:41.927"
      }
    ]
  },
  {
    "id": 322775219,
    "companyId": 8567346,
    "itemCode": "1005",
    "taxCodeId": 38559,
    "taxCode": "SI026666",
    "description": "Sign Installation - Permanent",
    "createdDate": "2025-05-29T20:54:10.947",
    "createdUserId": 6732394,
    "modifiedDate": "2025-05-29T20:54:10.947",
    "modifiedUserId": 6732394,
    "itemStatus": [
      {
        "Status": "Complete",
        "ModifiedDate": "2025-05-29T20:54:11.127"
      }
    ]
  },
  {
    "id": 322775264,
    "companyId": 8567346,
    "itemCode": "1006",
    "taxCodeId": 72721,
    "taxCode": "PM020760",
    "description": "Sign Production - All",
    "createdDate": "2025-05-29T20:56:19.74",
    "createdUserId": 6732394,
    "modifiedDate": "2025-05-29T20:56:19.74",
    "modifiedUserId": 6732394,
    "itemStatus": [
      {
        "Status": "Complete",
        "ModifiedDate": "2025-05-29T20:56:19.923"
      }
    ]
  },
  {
    "id": 322775302,
    "companyId": 8567346,
    "itemCode": "1007",
    "taxCodeId": 8551,
    "taxCode": "FR010000",
    "description": "Delivery Fee",
    "createdDate": "2025-05-29T20:59:54.66",
    "createdUserId": 6732394,
    "modifiedDate": "2025-05-29T20:59:54.66",
    "modifiedUserId": 6732394,
    "itemStatus": [
      {
        "Status": "Complete",
        "ModifiedDate": "2025-05-29T20:59:54.8"
      }
    ]
  },
  {
    "id": 322775331,
    "companyId": 8567346,
    "itemCode": "1008",
    "taxCodeId": 8554,
    "taxCode": "FR020000",
    "description": "Shipping Fee",
    "createdDate": "2025-05-29T21:01:42.23",
    "createdUserId": 6732394,
    "modifiedDate": "2025-05-29T21:01:42.23",
    "modifiedUserId": 6732394,
    "itemStatus": [
      {
        "Status": "Complete",
        "ModifiedDate": "2025-05-29T21:01:42.403"
      }
    ]
  },
  {
    "id": 332419947,
    "companyId": 8567346,
    "itemCode": "1010",
    "taxCodeId": 16740,
    "taxCode": "NT",
    "description": "Non-Taxable",
    "createdDate": "2025-08-06T16:17:32.037",
    "createdUserId": 6733326,
    "modifiedDate": "2025-08-06T16:17:32.037",
    "modifiedUserId": 6733326,
    "source": "AVATAXUI",
    "status": 1,
    "itemStatus": [
      {
        "Status": "Complete",
        "ModifiedDate": "2025-08-06T16:17:32.197"
      }
    ]
  }
]

// const uploadCatalog = async (request, reply) => {
//   try {
//     const currentDate = Math.round(moment().valueOf() / 1000);
//     const { data = [], userId } = request.body;
//     const successProducts = [];
//     const failedProducts = [];
//     quickbookId =null
//     const findAvalara = (code) => {
//       if (!code) return null;
//       const c = String(code).trim().toLowerCase();
//       return AVALARA_TAX_MAP.find(x => String(x.itemCode).trim().toLowerCase() === c) || null;
//     };

//     // Master unit list
//     const units = [
//       { id: 1, name: 'Square feet', short_name: 'Sq ft', unit_id: 1 },
//       { id: 2, name: 'Linear feet', short_name: 'Lin ft', unit_id: 1 },
//       { id: 3, name: 'Square inches', short_name: 'Sq in', unit_id: 1 },
//       { id: 4, name: 'Unit', short_name: 'Unit', unit_id: 2 },
//       { id: 5, name: 'Gallons', short_name: 'Gal', unit_id: 3 },
//       { id: 6, name: 'Quarts', short_name: 'Qt', unit_id: 3 },
//       { id: 7, name: 'Milliliter', short_name: 'ml', unit_id: 3 },
//       { id: 8, name: 'Liter', short_name: 'L', unit_id: 3 },
//       { id: 9, name: 'Pint', short_name: 'pt', unit_id: 3 },
//       { id: 10, name: 'Half Pint', short_name: 'half-pt', unit_id: 3 },
//       { id: 11, name: 'Ounce', short_name: 'oz', unit_id: 3 },
//       { id: 12, name: 'Kilogram', short_name: 'kg', unit_id: 4 },
//       { id: 13, name: 'Gram', short_name: 'g', unit_id: 4 },
//       { id: 14, name: 'Milligram', short_name: 'mg', unit_id: 4 },
//       { id: 15, name: 'Each', short_name: 'EA', unit_id: 2 },
//     ];

//     const norm = (v) => (typeof v === 'string' ? v.trim() : v);
//     const toNumberOr = (v, fallback = 0) => {
//       const n = Number(v);
//       return Number.isFinite(n) ? n : fallback;
//     };
//     const numOrNaN = (v) => {
//       const n = Number(v);
//       return Number.isFinite(n) ? n : NaN;
//     };
//     const findUnit = (raw) => {
//       const s = norm(raw);
//       if (!s) return null;
//       const low = s.toLowerCase();
//       return (
//         units.find((u) => u.name.toLowerCase() === low) ||
//         units.find((u) => (u.short_name || '').toLowerCase() === low)
//       );
//     };

//     let inserted = 0;

//     for (let i = 0; i < data.length; i++) {
//       try {
//         const row = data[i] || {};
//         const name = norm(row.name);
//         const description = norm(row.description);
//         const sku = norm(row.SKU) || '';
//         const rawUnit = row.unit;
//         const rawWorkflow = row.workflow;
//         const rawFormula = row.productFormula;
//         const rawCategory = row.category;
//         const rawSubCategory = row.subCategory;
//         const tags = row.tags;

//         // From Excel (new fields)
//         const excelRetail = numOrNaN(row.retailPrice);   // DB: actual_retail_price
//         const excelInstall = numOrNaN(row.installPrice); // DB: install_cost
//         const avalaraItemCode = norm(row.avalaraTaxCategory); // maps to taxCodeId + taxCode

//         if (!name) continue;

//         // Uniqueness guard
//         const productNameExist = await Product.isProductNameExist(name);
//         const isServiceNameExist = await Product.isServiceNameExist(name);
//         if (productNameExist || isServiceNameExist) continue;

//         // Category & Subcategory (optional)
//         let categoryId = null;
//         if (rawCategory && norm(rawCategory)) {
//           categoryId = await Product.checkCatalogCategoryExist(norm(rawCategory));
//         }
//         let sub_category_id = null;
//         if (categoryId && rawSubCategory && norm(rawSubCategory)) {
//           sub_category_id = await Product.checkMaterialSubCategoryExist(
//             norm(rawSubCategory),
//             categoryId.id
//           );
//         }

//         // Unit (optional)
//         const unitObj = findUnit(rawUnit);
//         const unitId = unitObj ? unitObj.id : null;

//         // Unit qty (default 1)
//         let unit_qty = 1;
//         if (row.numberOfUnit !== undefined && row.numberOfUnit !== null) {
//           const numUnits = toNumberOr(row.numberOfUnit, NaN);
//           if (Number.isInteger(numUnits) && numUnits > 0) unit_qty = numUnits;
//         }

//         // Workflow (optional)
//         let workflowId = null;
//         if (rawWorkflow && norm(rawWorkflow)) {
//           workflowId = await Product.checkWorkflowExist(norm(rawWorkflow));
//         }

//         // Recipe (optional)
//         let recipe = null;
//         if (rawFormula && norm(rawFormula)) {
//           recipe = await Product.checkRecipeExist(norm(rawFormula));
//         }

//         // Pricing
//         const expectedMaterial = recipe ? toNumberOr(recipe.expected_material_cost, 0) : 0;
//         const expectedLabor = recipe ? toNumberOr(recipe.expected_total_service_cost, 0) : 0;
//         const providedCost = toNumberOr(row.cost, 0);

//         let wholesalePrice;
//         let retailPriceComputed;

//         if (recipe) {
//           let subCatData = null;
//           if (sub_category_id) {
//             subCatData = await Product.checkCatalogSubCategoryExist(sub_category_id);
//           }
//           const markup = subCatData ? toNumberOr(subCatData.markup, 0) : 0;
//           const wholesaleMarkup = subCatData ? toNumberOr(subCatData.wholesale_markup, 0) : 0;

//           const base = expectedLabor + expectedMaterial;
//           retailPriceComputed = base + (markup * expectedMaterial) / 100;
//           wholesalePrice = base + (wholesaleMarkup * expectedMaterial) / 100;

//           if (!Number.isFinite(retailPriceComputed)) retailPriceComputed = base;
//           if (!Number.isFinite(wholesalePrice)) wholesalePrice = base;
//         } else {
//           let subCatData = null;
//           if (sub_category_id) {
//             subCatData = await Product.checkCatalogSubCategoryExist(sub_category_id);
//           }
//           const markup = subCatData ? toNumberOr(subCatData.markup, 0) : 0;
//           const wholesaleMarkup = subCatData ? toNumberOr(subCatData.wholesale_markup, 0) : 0;

//           const base = providedCost;
//           retailPriceComputed = base + (markup * base) / 100;
//           wholesalePrice = base + (wholesaleMarkup * base) / 100;

//           if (!Number.isFinite(retailPriceComputed)) retailPriceComputed = base;
//           if (!Number.isFinite(wholesalePrice)) wholesalePrice = base;
//         }

//         // Excel-driven overrides
//         const hasExcelRetail = Number.isFinite(excelRetail);
//         const hasExcelInstall = Number.isFinite(excelInstall);

//         const actual_retail_price = hasExcelRetail ? excelRetail : null;
//         const install_cost = hasExcelInstall ? excelInstall : null;

//         const retail_price_final =
//           (hasExcelRetail || hasExcelInstall)
//             ? ((hasExcelRetail ? excelRetail : 0) + (hasExcelInstall ? excelInstall : 0))
//             : retailPriceComputed;

//         // Avalara mapping
//         const avalara = findAvalara(avalaraItemCode);
//         const tax_code_id = avalara ? avalara.taxCodeId : null;
//         const tax_code = avalara ? avalara.taxCode : null;

//         // Build payload for DB/QB
//         const productData = {
//           name,
//           description: description || '',
//           vpt_number: sku,
//           unit: unitId,
//           unit_qty,
//           special_price: recipe ? expectedMaterial : providedCost, // "cost"
//           price: wholesalePrice,
//           retail_price: retail_price_final,
//           actual_retail_price,  // from Excel
//           install_cost,         // from Excel
//           workflow: workflowId || null,
//           recipe_id: recipe ? recipe.id : null,
//           tax_category: 0,      // keep if your schema expects it
//           taxCodeId: tax_code_id, // Avalara
//           taxCode: tax_code,      // Avalara
//           timestamp: currentDate,
//           added_by: userId || 1,
//           tags,
//           status: 1,
//           category_id: categoryId ? categoryId.id : null,
//           sub_category_id: sub_category_id || null,
//         };

//         // Minimums before QB call (avoid noisy QB errors)
//         const hasMinimums =
//           Boolean(productData.name) &&
//           Boolean(productData.description) &&
//           Number.isFinite(toNumberOr(productData.price, NaN)) &&
//           Number.isFinite(toNumberOr(productData.special_price, NaN)); // QB needs a cost

//         if (!hasMinimums) {
//           console.log(`[SKIP] Minimums not satisfied for "${productData.name}".`);
//           continue;
//         }

//         // ─── QuickBooks sync is MANDATORY ───

//         try {
//           const sampleProductData = {
//             name: productData.name,
//             unit_qty: productData.unit_qty || 0,
//             cost: productData.special_price || 0,
//             wholeSalePrice: productData.price || 0,
//             retailPrice: productData.retail_price || 0,
//             sku: productData.vpt_number || '',
//           };
//           const qbRes = await addProductToQuickBooks(sampleProductData);


//         } catch (e) {
//           console.log(`[QB] Create failed for "${productData.name}":`, e?.message || e);
//           continue; // mandatory: skip on QB failure
//         }

//         if(quickbookId){
//           productData.syncWithQuickbooks = 1;
//           productData.quickbookId = quickbookId;
//         }

//         // FINAL INSERT
//         const result = await Product.addProduct(productData);
//         if (result) inserted++;
//       } catch (rowErr) {
//         console.log('[ROW ERROR] Skipping row due to error:', rowErr?.message || rowErr);
//         continue;
//       }
//     }

//     const ignored = data.length - inserted;

//     if (inserted > 0) {
//       reply.send({ status: 1, message: `${inserted} rows effected, ${ignored} ignored` });

//       const notificationMsg = `New Catalog uploaded! ${inserted} rows effected, ${ignored} ignored`;
//       const notificationUrl = 'products';
//       const actorId = userId || 1;

//       await Admin.incrementField(actorId, 'noti_count', 1);
//       await Admin.addNotification({
//         user_id: actorId,
//         url: notificationUrl,
//         timestamp: currentDate,
//         message: notificationMsg,
//         status: 1,
//         from_id: actorId,
//       });

//       const adminIds = await Admin.getAllAdminIds();
//       for (const adminId of adminIds) {
//         if (adminId !== actorId) {
//           await Admin.incrementField(adminId, 'noti_count', 1);
//           await Admin.addNotification({
//             user_id: adminId,
//             url: notificationUrl,
//             timestamp: currentDate,
//             message: notificationMsg,
//             status: 1,
//             from_id: actorId,
//           });
//         }
//       }
//     } else {
//       reply.send({
//         status: 0,
//         message: 'Failed to upload the catalog. Ensure at least name, description, and a cost/price are provided, and that QuickBooks creation succeeds.',
//       });
//     }
//   } catch (error) {
//     console.log('###############Error', error);
//     reply.send({ status: 0, message: 'Failed to upload the catalog', error });
//   }
// };


const uploadCatalog = async (request, reply) => {
  try {
    const currentDate = Math.round(moment().valueOf() / 1000);
    const { data = [], userId } = request.body;
    const successProducts = [];
    const failedProducts = [];
    quickbookId = null;

    const findAvalara = (code) => {
      if (!code) return null;
      const c = String(code).trim().toLowerCase();
      return AVALARA_TAX_MAP.find(
        (x) => String(x.itemCode).trim().toLowerCase() === c
      ) || null;
    };

    const units = [
      { id: 1, name: "Square feet", short_name: "Sq ft", unit_id: 1 },
      { id: 2, name: "Linear feet", short_name: "Lin ft", unit_id: 1 },
      { id: 3, name: "Square inches", short_name: "Sq in", unit_id: 1 },
      { id: 4, name: "Unit", short_name: "Unit", unit_id: 2 },
      { id: 5, name: "Gallons", short_name: "Gal", unit_id: 3 },
      { id: 6, name: "Quarts", short_name: "Qt", unit_id: 3 },
      { id: 7, name: "Milliliter", short_name: "ml", unit_id: 3 },
      { id: 8, name: "Liter", short_name: "L", unit_id: 3 },
      { id: 9, name: "Pint", short_name: "pt", unit_id: 3 },
      { id: 10, name: "Half Pint", short_name: "half-pt", unit_id: 3 },
      { id: 11, name: "Ounce", short_name: "oz", unit_id: 3 },
      { id: 12, name: "Kilogram", short_name: "kg", unit_id: 4 },
      { id: 13, name: "Gram", short_name: "g", unit_id: 4 },
      { id: 14, name: "Milligram", short_name: "mg", unit_id: 4 },
      { id: 15, name: "Each", short_name: "EA", unit_id: 2 },
    ];

    const norm = (v) => (typeof v === "string" ? v.trim() : v);
    const toNumberOr = (v, fallback = 0) => {
      const n = Number(v);
      return Number.isFinite(n) ? n : fallback;
    };
    const numOrNaN = (v) => {
      const n = Number(v);
      return Number.isFinite(n) ? n : NaN;
    };
    const findUnit = (raw) => {
      const s = norm(raw);
      if (!s) return null;
      const low = s.toLowerCase();
      return (
        units.find((u) => u.name.toLowerCase() === low) ||
        units.find((u) => (u.short_name || "").toLowerCase() === low)
      );
    };

    let inserted = 0;

    for (let i = 0; i < data.length; i++) {
      try {
        const row = data[i] || {};
        const name = norm(row.name);
        const description = norm(row.description);
        const sku = norm(row.SKU) || "";
        const rawUnit = row.unit;
        const rawWorkflow = row.workflow;
        const rawFormula = row.productFormula;
        const rawCategory = row.category;
        const rawSubCategory = row.subCategory;
        const tags = row.tags;
        const lead_time = row.leadTime || null; // 🆕 New field

        const excelRetail = numOrNaN(row.retailPrice);
        const excelInstall = numOrNaN(row.installPrice);
        const avalaraItemCode = norm(row.avalaraTaxCategory);

        if (!name) {
          failedProducts.push("(no name)");
          continue;
        }

        const productNameExist = await Product.isProductNameExist(name);
        const isServiceNameExist = await Product.isServiceNameExist(name);
        if (productNameExist || isServiceNameExist) {
          failedProducts.push(name);
          continue;
        }

        let categoryId = null;
        if (rawCategory && norm(rawCategory)) {
          categoryId = await Product.checkCatalogCategoryExist(norm(rawCategory));
        }
        let sub_category_id = null;
        if (categoryId && rawSubCategory && norm(rawSubCategory)) {
          sub_category_id = await Product.checkMaterialSubCategoryExist(
            norm(rawSubCategory),
            categoryId.id
          );
        }

        const unitObj = findUnit(rawUnit);
        const unitId = unitObj ? unitObj.id : null;

        let unit_qty = 1;
        if (row.numberOfUnit !== undefined && row.numberOfUnit !== null) {
          const numUnits = toNumberOr(row.numberOfUnit, NaN);
          if (Number.isInteger(numUnits) && numUnits > 0) unit_qty = numUnits;
        }

        let workflowId = null;
        if (rawWorkflow && norm(rawWorkflow)) {
          workflowId = await Product.checkWorkflowExist(norm(rawWorkflow));
        }

        let recipe = null;
        if (rawFormula && norm(rawFormula)) {
          recipe = await Product.checkRecipeExist(norm(rawFormula));
        }

        const expectedMaterial = recipe ? toNumberOr(recipe.expected_material_cost, 0) : 0;
        const expectedLabor = recipe ? toNumberOr(recipe.expected_total_service_cost, 0) : 0;
        const providedCost = toNumberOr(row.cost, 0);

        let wholesalePrice;
        let retailPriceComputed;

        if (recipe) {
          let subCatData = null;
          if (sub_category_id) {
            subCatData = await Product.checkCatalogSubCategoryExist(sub_category_id);
          }
          const markup = subCatData ? toNumberOr(subCatData.markup, 0) : 0;
          const wholesaleMarkup = subCatData ? toNumberOr(subCatData.wholesale_markup, 0) : 0;
          const base = expectedLabor + expectedMaterial;
          retailPriceComputed = base + (markup * expectedMaterial) / 100;
          wholesalePrice = base + (wholesaleMarkup * expectedMaterial) / 100;
        } else {
          let subCatData = null;
          if (sub_category_id) {
            subCatData = await Product.checkCatalogSubCategoryExist(sub_category_id);
          }
          const markup = subCatData ? toNumberOr(subCatData.markup, 0) : 0;
          const wholesaleMarkup = subCatData ? toNumberOr(subCatData.wholesale_markup, 0) : 0;
          const base = providedCost;
          retailPriceComputed = base + (markup * base) / 100;
          wholesalePrice = base + (wholesaleMarkup * base) / 100;
        }

        const hasExcelRetail = Number.isFinite(excelRetail);
        const hasExcelInstall = Number.isFinite(excelInstall);

        const actual_retail_price = hasExcelRetail ? excelRetail : null;
        const install_cost = hasExcelInstall ? excelInstall : null;

        const retail_price_final =
          hasExcelRetail || hasExcelInstall
            ? (hasExcelRetail ? excelRetail : 0) +
            (hasExcelInstall ? excelInstall : 0)
            : retailPriceComputed;

        const avalara = findAvalara(avalaraItemCode);
        const tax_code_id = avalara ? avalara.taxCodeId : null;
        const tax_code = avalara ? avalara.taxCode : null;

        const productData = {
          name,
          description: description || "",
          vpt_number: sku,
          unit: unitId,
          unit_qty,
          special_price: recipe ? expectedMaterial : providedCost,
          price: wholesalePrice,
          retail_price: retail_price_final,
          actual_retail_price,
          install_cost,
          workflow: workflowId || null,
          recipe_id: recipe ? recipe.id : null,
          tax_category: 0,
          taxCodeId: tax_code_id,
          taxCode: tax_code,
          timestamp: currentDate,
          added_by: userId || 1,
          tags,
          status: 1,
          category_id: categoryId ? categoryId.id : null,
          sub_category_id: sub_category_id || null,
          lead_time, // 🆕
        };

        // Attempt QuickBooks
        try {
          const sampleProductData = {
            name: productData.name,
            unit_qty: productData.unit_qty || 0,
            cost: productData.special_price || 0,
            wholeSalePrice: productData.price || 0,
            retailPrice: productData.retail_price || 0,
            sku: productData.vpt_number || "",
          };
          const qbRes = await addProductToQuickBooks(sampleProductData);
        } catch (e) {
          console.log(`[QB] Create failed for "${productData.name}":`, e?.message || e);
          failedProducts.push(name);
          continue;
        }

        if (quickbookId) {
          productData.syncWithQuickbooks = 1;
          productData.quickbookId = quickbookId;
        }

        const result = await Product.addProduct(productData);
        if (result) {
          inserted++;
          successProducts.push(name);
        } else {
          failedProducts.push(name);
        }
      } catch (rowErr) {
        console.log("[ROW ERROR]", rowErr?.message || rowErr);
        failedProducts.push(`(error: ${rowErr?.message || "unknown"})`);
        continue;
      }
    }

    const ignored = data.length - inserted;

    // ✅ Always return both success & failed products
    reply.send({
      status: inserted > 0 ? 1 : 0,
      message:
        inserted > 0
          ? `${inserted} rows effected, ${ignored} ignored`
          : "Failed to upload the catalog. Ensure at least name, description, and a cost/price are provided, and that QuickBooks creation succeeds.",
      successProducts,
      failedProducts,
    });

    // Notifications only if successful
    if (inserted > 0) {
      const notificationMsg = `New Catalog uploaded! ${inserted} rows effected, ${ignored} ignored`;
      const notificationUrl = "products";
      const actorId = userId || 1;

      await Admin.incrementField(actorId, "noti_count", 1);
      await Admin.addNotification({
        user_id: actorId,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: actorId,
      });

      const adminIds = await Admin.getAllAdminIds();
      for (const adminId of adminIds) {
        if (adminId !== actorId) {
          await Admin.incrementField(adminId, "noti_count", 1);
          await Admin.addNotification({
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: actorId,
          });
        }
      }
    }
  } catch (error) {
    console.log("###############Error", error);
    reply.send({
      status: 0,
      message: "Failed to upload the catalog",
      error,
      failedProducts: [],
    });
  }
};


// const uploadCatalog = async (request, reply) => {
//   try {
//     const currentDate = Math.round(moment().valueOf() / 1000);
//     const { data = [], userId } = request.body;
//     const successProducts = [];
//     const failedProducts = [];
//     quickbookId = null;

//     const findAvalara = (code) => {
//       if (!code) return null;
//       const c = String(code).trim().toLowerCase();
//       return AVALARA_TAX_MAP.find(
//         (x) => String(x.itemCode).trim().toLowerCase() === c
//       ) || null;
//     };

//     // Master unit list
//     const units = [
//       { id: 1, name: "Square feet", short_name: "Sq ft", unit_id: 1 },
//       { id: 2, name: "Linear feet", short_name: "Lin ft", unit_id: 1 },
//       { id: 3, name: "Square inches", short_name: "Sq in", unit_id: 1 },
//       { id: 4, name: "Unit", short_name: "Unit", unit_id: 2 },
//       { id: 5, name: "Gallons", short_name: "Gal", unit_id: 3 },
//       { id: 6, name: "Quarts", short_name: "Qt", unit_id: 3 },
//       { id: 7, name: "Milliliter", short_name: "ml", unit_id: 3 },
//       { id: 8, name: "Liter", short_name: "L", unit_id: 3 },
//       { id: 9, name: "Pint", short_name: "pt", unit_id: 3 },
//       { id: 10, name: "Half Pint", short_name: "half-pt", unit_id: 3 },
//       { id: 11, name: "Ounce", short_name: "oz", unit_id: 3 },
//       { id: 12, name: "Kilogram", short_name: "kg", unit_id: 4 },
//       { id: 13, name: "Gram", short_name: "g", unit_id: 4 },
//       { id: 14, name: "Milligram", short_name: "mg", unit_id: 4 },
//       { id: 15, name: "Each", short_name: "EA", unit_id: 2 },
//     ];

//     const norm = (v) => (typeof v === "string" ? v.trim() : v);
//     const toNumberOr = (v, fallback = 0) => {
//       const n = Number(v);
//       return Number.isFinite(n) ? n : fallback;
//     };
//     const numOrNaN = (v) => {
//       const n = Number(v);
//       return Number.isFinite(n) ? n : NaN;
//     };
//     const findUnit = (raw) => {
//       const s = norm(raw);
//       if (!s) return null;
//       const low = s.toLowerCase();
//       return (
//         units.find((u) => u.name.toLowerCase() === low) ||
//         units.find((u) => (u.short_name || "").toLowerCase() === low)
//       );
//     };

//     let inserted = 0;

//     for (let i = 0; i < data.length; i++) {
//       try {
//         const row = data[i] || {};
//         const name = norm(row.name);
//         const description = norm(row.description);
//         const sku = norm(row.SKU) || "";
//         const rawUnit = row.unit;
//         const rawWorkflow = row.workflow;
//         const rawFormula = row.productFormula;
//         const rawCategory = row.category;
//         const rawSubCategory = row.subCategory;
//         const tags = row.tags;
//         const lead_time = row.leadTime || null; // 🆕 New field

//         // From Excel (new fields)
//         const excelRetail = numOrNaN(row.retailPrice); // DB: actual_retail_price
//         const excelInstall = numOrNaN(row.installPrice); // DB: install_cost
//         const avalaraItemCode = norm(row.avalaraTaxCategory); // maps to taxCodeId + taxCode

//         if (!name) continue;

//         // Uniqueness guard
//         const productNameExist = await Product.isProductNameExist(name);
//         const isServiceNameExist = await Product.isServiceNameExist(name);
//         if (productNameExist || isServiceNameExist) continue;

//         // Category & Subcategory (optional)
//         let categoryId = null;
//         if (rawCategory && norm(rawCategory)) {
//           categoryId = await Product.checkCatalogCategoryExist(norm(rawCategory));
//         }
//         let sub_category_id = null;
//         if (categoryId && rawSubCategory && norm(rawSubCategory)) {
//           sub_category_id = await Product.checkMaterialSubCategoryExist(
//             norm(rawSubCategory),
//             categoryId.id
//           );
//         }

//         // Unit (optional)
//         const unitObj = findUnit(rawUnit);
//         const unitId = unitObj ? unitObj.id : null;

//         // Unit qty (default 1)
//         let unit_qty = 1;
//         if (row.numberOfUnit !== undefined && row.numberOfUnit !== null) {
//           const numUnits = toNumberOr(row.numberOfUnit, NaN);
//           if (Number.isInteger(numUnits) && numUnits > 0) unit_qty = numUnits;
//         }

//         // Workflow (optional)
//         let workflowId = null;
//         if (rawWorkflow && norm(rawWorkflow)) {
//           workflowId = await Product.checkWorkflowExist(norm(rawWorkflow));
//         }

//         // Recipe (optional)
//         let recipe = null;
//         if (rawFormula && norm(rawFormula)) {
//           recipe = await Product.checkRecipeExist(norm(rawFormula));
//         }

//         // Pricing
//         const expectedMaterial = recipe ? toNumberOr(recipe.expected_material_cost, 0) : 0;
//         const expectedLabor = recipe ? toNumberOr(recipe.expected_total_service_cost, 0) : 0;
//         const providedCost = toNumberOr(row.cost, 0);

//         let wholesalePrice;
//         let retailPriceComputed;

//         if (recipe) {
//           let subCatData = null;
//           if (sub_category_id) {
//             subCatData = await Product.checkCatalogSubCategoryExist(sub_category_id);
//           }
//           const markup = subCatData ? toNumberOr(subCatData.markup, 0) : 0;
//           const wholesaleMarkup = subCatData ? toNumberOr(subCatData.wholesale_markup, 0) : 0;

//           const base = expectedLabor + expectedMaterial;
//           retailPriceComputed = base + (markup * expectedMaterial) / 100;
//           wholesalePrice = base + (wholesaleMarkup * expectedMaterial) / 100;

//           if (!Number.isFinite(retailPriceComputed)) retailPriceComputed = base;
//           if (!Number.isFinite(wholesalePrice)) wholesalePrice = base;
//         } else {
//           let subCatData = null;
//           if (sub_category_id) {
//             subCatData = await Product.checkCatalogSubCategoryExist(sub_category_id);
//           }
//           const markup = subCatData ? toNumberOr(subCatData.markup, 0) : 0;
//           const wholesaleMarkup = subCatData ? toNumberOr(subCatData.wholesale_markup, 0) : 0;

//           const base = providedCost;
//           retailPriceComputed = base + (markup * base) / 100;
//           wholesalePrice = base + (wholesaleMarkup * base) / 100;

//           if (!Number.isFinite(retailPriceComputed)) retailPriceComputed = base;
//           if (!Number.isFinite(wholesalePrice)) wholesalePrice = base;
//         }

//         // Excel-driven overrides
//         const hasExcelRetail = Number.isFinite(excelRetail);
//         const hasExcelInstall = Number.isFinite(excelInstall);

//         const actual_retail_price = hasExcelRetail ? excelRetail : null;
//         const install_cost = hasExcelInstall ? excelInstall : null;

//         const retail_price_final =
//           hasExcelRetail || hasExcelInstall
//             ? (hasExcelRetail ? excelRetail : 0) +
//               (hasExcelInstall ? excelInstall : 0)
//             : retailPriceComputed;

//         // Avalara mapping
//         const avalara = findAvalara(avalaraItemCode);
//         const tax_code_id = avalara ? avalara.taxCodeId : null;
//         const tax_code = avalara ? avalara.taxCode : null;

//         // Build payload for DB/QB
//         const productData = {
//           name,
//           description: description || "",
//           vpt_number: sku,
//           unit: unitId,
//           unit_qty,
//           special_price: recipe ? expectedMaterial : providedCost,
//           price: wholesalePrice,
//           retail_price: retail_price_final,
//           actual_retail_price,
//           install_cost,
//           workflow: workflowId || null,
//           recipe_id: recipe ? recipe.id : null,
//           tax_category: 0,
//           taxCodeId: tax_code_id,
//           taxCode: tax_code,
//           timestamp: currentDate,
//           added_by: userId || 1,
//           tags,
//           status: 1,
//           category_id: categoryId ? categoryId.id : null,
//           sub_category_id: sub_category_id || null,
//           lead_time, // 🆕 newly added field
//         };
//          console.log("productData 1",productData)
//         // Minimums before QB call
//         // const hasMinimums =
//         //   Boolean(productData.name) &&
//         //   Boolean(productData.description) 
//         //   // Number.isFinite(toNumberOr(productData.price, NaN)) &&
//         //   // Number.isFinite(toNumberOr(productData.special_price, NaN));

//         // if (!hasMinimums) {
//         //   console.log(`[SKIP] Minimums not satisfied for "${productData.name}".`);
//         //   continue;
//         // }

//         // ─── QuickBooks sync is MANDATORY ───
//         try {
//           const sampleProductData = {
//             name: productData.name,
//             unit_qty: productData.unit_qty || 0,
//             cost: productData.special_price || 0,
//             wholeSalePrice: productData.price || 0,
//             retailPrice: productData.retail_price || 0,
//             sku: productData.vpt_number || "",
//           };
//           const qbRes = await addProductToQuickBooks(sampleProductData);
//         } catch (e) {
//           console.log(`[QB] Create failed for "${productData.name}":`, e?.message || e);
//           continue;
//         }

//         if (quickbookId) {
//           productData.syncWithQuickbooks = 1;
//           productData.quickbookId = quickbookId;
//         }
//         console.log("productData 2",productData) 
//         // FINAL INSERT
//         const result = await Product.addProduct(productData);
//         console.log('productData 3',result)
//         if (result) {
//           inserted++;
//         }else{
//           failedProducts.push(productData.name);
//         }
//       } catch (rowErr) {
//         console.log("[ROW ERROR] Skipping row due to error:", rowErr?.message || rowErr);
//         continue;
//       }
//     }

//     const ignored = data.length - inserted;

//     if (inserted > 0) {
//       reply.send({ status: 1, message: `${inserted} rows effected, ${ignored} ignored`,failedProducts:failedProducts });

//       const notificationMsg = `New Catalog uploaded! ${inserted} rows effected, ${ignored} ignored`;
//       const notificationUrl = "products";
//       const actorId = userId || 1;

//       await Admin.incrementField(actorId, "noti_count", 1);
//       await Admin.addNotification({
//         user_id: actorId,
//         url: notificationUrl,
//         timestamp: currentDate,
//         message: notificationMsg,
//         status: 1,
//         from_id: actorId,
//       });

//       const adminIds = await Admin.getAllAdminIds();
//       for (const adminId of adminIds) {
//         if (adminId !== actorId) {
//           await Admin.incrementField(adminId, "noti_count", 1);
//           await Admin.addNotification({
//             user_id: adminId,
//             url: notificationUrl,
//             timestamp: currentDate,
//             message: notificationMsg,
//             status: 1,
//             from_id: actorId,
//           });
//         }
//       }
//     } else {
//       reply.send({
//         status: 0,
//         failedProducts:failedProducts,
//         message:
//           "Failed to upload the catalog. Ensure at least name, description, and a cost/price are provided, and that QuickBooks creation succeeds.",
//       });
//     }
//   } catch (error) {
//     console.log("###############Error", error);
//     reply.send({ status: 0, message: "Failed to upload the catalog", error });
//   }
// };



const getAllMeterialProducts = async (request, reply) => {
  try {
    const { userType, userId, isVendor, page, rowPerpage, search, bringStockCatalog } = request.body
    console.log("jhdsfdf", request.body);

    // let results = await Product.getAllMaterials(true);
    let results = await Product.getAllMaterialsMain(true);
    let allServices = await Product.getAllServices();
    let dataset = await Product.getDataSet()
    let productCategories = await Product.getAllProductCategories(false, " and  p.category_type=2");
    let vendorData = []
    if (userType || userId || isVendor) {
      vendorData = await Client.getAllClients(true, userType, userId, page, rowPerpage, search, isVendor);
    }
    for (const data of productCategories) {
      let subData = await Product.getSubCatFromCategoryId(data.id)
      let subArray = []
      for (const ss of subData) {
        subArray.push(ss.name)
      }
      data.sub_category = subArray
      data.subCategoryInfo = subData
    }
    let stockCatalogs = []
    if (bringStockCatalog) {
      stockCatalogs = await Product.getAllStockCatalogForPO(true);
    }
    if (!empty(results)) {
      reply.send({ status: 1, dataset, results, productCategories, vendorData, allServices, stockCatalogs, message: "Data fetched successfully" });
    } else {
      reply.send({ status: 0, results, productCategories, vendorData, allServices, stockCatalogs, message: "Error fetching data" });
    }
  } catch (error) {
    console.log("Server error", error);
    reply.status(500).send("Server error" + error);
  }
};
const getAllMeterialProductsFiltered = async (request, reply) => {
  try {
    const {
      userType,
      userId,
      isVendor,
      page,
      rowPerpage,
      rowsPerPage,
      search,
      bringStockCatalog,
      materialTab,
      categoryIds,
      subCategoryIds,
    } = request.body;

    const explicitPagination =
      request.body.page != null ||
      request.body.rowsPerPage != null ||
      request.body.rowPerpage != null;

    let results;
    let totalCount = 0;
    let pagination = null;

    if (explicitPagination) {
      const pageNum = Number(page) > 0 ? Number(page) : 1;
      const limitNum =
        Number(rowsPerPage) > 0
          ? Number(rowsPerPage)
          : Number(rowPerpage) > 0
            ? Number(rowPerpage)
            : 50;
      const agg = await Product.getAllFilteredMaterialsMain({
        page: pageNum,
        limit: limitNum,
        search: String(search || '').trim(),
        materialTab: materialTab || null,
        categoryIds: Array.isArray(categoryIds) ? categoryIds : [],
        subCategoryIds: Array.isArray(subCategoryIds) ? subCategoryIds : [],
      });
      results = agg.results;
      totalCount = agg.totalCount;
      pagination = agg.pagination;
    } else {
      results = await Product.getAllMaterialsMain(true);
      totalCount = Array.isArray(results) ? results.length : 0;
    }

    let allServices = await Product.getAllServices();
    let dataset = await Product.getDataSet()
    let productCategories = await Product.getAllProductCategories(false, " and  p.category_type=2");
    let vendorData = []
    if (userType || userId || isVendor) {
      vendorData = await Client.getAllClients(
        true,
        userType,
        userId,
        explicitPagination ? undefined : page,
        explicitPagination ? undefined : rowPerpage,
        explicitPagination ? undefined : search,
        isVendor
      );
    }
    for (const data of productCategories) {
      let subData = await Product.getSubCatFromCategoryId(data.id)
      let subArray = []
      for (const ss of subData) {
        subArray.push(ss.name)
      }
      data.sub_category = subArray
      data.subCategoryInfo = subData
    }
    let stockCatalogs = []
    if (bringStockCatalog) {
      stockCatalogs = await Product.getAllStockCatalogForPO(true);
    }
    const payload = {
      status: 1,
      dataset,
      results,
      productCategories,
      vendorData,
      allServices,
      stockCatalogs,
      totalCount,
      pagination,
      message: "Data fetched successfully"
    };
    if (explicitPagination) {
      reply.send(payload);
    } else if (!empty(results)) {
      reply.send(payload);
    } else {
      reply.send({ status: 0, results, productCategories, vendorData, allServices, stockCatalogs, message: "Error fetching data" });
    }
  } catch (error) {
    console.log("Server error", error);
    reply.status(500).send("Server error" + error);
  }
};
const checkAllMeterialStock = async (request, reply) => {
  try {
    let products = await Product.getAllMaterialsOutOfStock();
    let admins = await Admin.getAllAdminIds()
    let notification = ""
    let notificationEmail = "<table><tr><th>Product Name</th><th>Stock Left</th><th>Minimum Quantity</th></tr>";
    products.forEach(product => {
      // if (product.total_qty < product.min_value) {
      notification += `Product ${product.name} is low on stock. Stock left: ${product.total_qty}, Minimum quantity needed: ${product.min_value}<br/>`
      notificationEmail += `<tr><td>${product.name}</td><td>${product.total_qty}</td><td>${product.min_value}</td></tr>`;
      // }

    });

    let notificationData = {};
    for (const adminId of admins) {

      const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
      notificationData = {
        user_id: adminId,
        url: "",
        timestamp: moment().unix(),
        message: notification,
        status: 1,
        from_id: 1,
      }
      const addNotificationResult = await Admin.addNotification(notificationData)

    }
    let welcome = "Hello Admin,"
    let email_content = notificationEmail
    let subject = " Stock Low:Notification"
    let message = rfqEmail()
    message = message.replace('[emailContent]', email_content)
    message = message.replace('[CONTENT]', welcome)

    message = message.replace('[LOGIN_URL]', `${BASE_URL}`)
    let adminEmails = await Admin.getAllAdminEmails();
    for (const row of adminEmails) {
      console.log("$$$$$$$$$$$$$$$nush", row);
      // const msg = {
      //   to: row, // Change to your recipient
      //   from: 'nushara@skyniche.com', // Change to your verified sender
      //   subject: subject,
      //   text: subject,
      //   html: message,
      // }
      // sgMail
      //   .send(msg)
      //   .then(() => {
      //     console.log('Email sent')
      //   })
      //   .catch((error) => {
      //     console.error(error)
      //   })


      const mailOptions = {
        to: row,
        from: '"SignWise Solutions" <noreply@signwisesolutions.com>', //'nushara@skyniche.com', // Change to your verified sender
        subject: subject,
        text: subject,
        html: message,
      }
      try {
        if (SEND_EMAIL) {
          await transporter.sendMail(mailOptions);
          console.log('Email sent');
        }
      } catch (error) {
        console.error('##########Err', error);

      }
    }
  } catch (error) {
    console.log("Server error", error);
    reply.status(500).send("Server error" + error);
  }
};

// Get last unit cost for a vendor-material combination
const getLastVendorMaterialUnitCost = async (req, res) => {
  try {
    const { vendor_id, material_id } = req.body;

    if (!vendor_id || !material_id) {
      return res.send({
        status: 0,
        message: 'Vendor ID and Material ID are required'
      });
    }

    const unitCost = await Product.getLastVendorMaterialUnitCost(vendor_id, material_id);

    if (unitCost !== null) {
      res.send({
        status: 1,
        unit_cost: unitCost,
        message: 'Unit cost fetched successfully'
      });
    } else {
      res.send({
        status: 1,
        unit_cost: null,
        message: 'No previous unit cost found for this vendor-material combination'
      });
    }
  } catch (error) {
    console.error('Error fetching last vendor material unit cost:', error);
    res.send({
      status: 0,
      message: 'Error fetching unit cost',
      error: error.message
    });
  }
};

const getTaxCategory = async (request, reply) => {
  try {
    const { bringProjectTypes = false } = request.body ?? { bringProjectTypes: false }
    const result = await Product.getTaxCategory();

    let projectTypes = []
    if (bringProjectTypes) {
      projectTypes = await Project.getAllProjectTypes(true);
    }
    if (result) {
      reply.send({ status: 1, message: 'Tax category fetched successfully', taxResult: result, projectTypes })
    }
    else {
      reply.send({ status: 0, message: 'Error fetching Tax category' })
    }
  }
  catch (error) {
    console.log("getTaxCategory Error : ", error);
    reply.send({ status: 500, message: 'Error', getTaxCategoryError: error })
  }
}

const getAllTaxCategory = async (request, reply) => {
  try {
    const result = await Product.getAllTaxCategory();
    if (result) {
      reply.send({ status: 1, message: 'Tax category fetched successfully', taxResult: result })
    }
    else {
      reply.send({ status: 0, message: 'Error fetching Tax category' })
    }
  }
  catch (error) {
    console.log("getTaxCategory Error : ", error);
    reply.send({ status: 500, message: 'Error', getTaxCategoryError: error })
  }
}

const getTaxCategoryById = async (request, reply) => {
  try {
    const { id } = request.body;
    const result = await Product.getTaxCategoryById(id);
    if (result) {
      reply.send({ status: 1, message: 'Tax category data fetched successfully', taxResult: result })
    } else {
      reply.send({ status: 0, message: 'Error fetching Tax category data' })
    }
  }
  catch (error) {
    console.log("getTaxCategoryById Error : ", error);
    reply.send({ status: 500, message: 'Error', getTaxCategoryByIdError: error })
  }
}

const addTaxCategory = async (request, reply) => {
  try {
    const { userId, values } = request.body;
    let currentDate = Math.round(moment().valueOf() / 1000);
    const taxData = {
      name: values.name,
      value: values.taxValue,
      type: values.type?.id,
      added_by: userId,
      timestamp: currentDate,
      status: 1
    }
    const result = await Product.addTaxCategory(taxData);
    if (result) {
      // Notification
      let notificationMsg = `New Tax category "${taxData.name}" has been added.`;
      let notificationUrl = "tax_category";

      reply.send({ status: 1, message: `Tax category "${taxData.name}" added successfully` })

      // Alert to user of new added product

      const incrementResult_User = await Admin.incrementField(taxData.added_by, "noti_count", 1);

      let notificationData_user = {
        user_id: taxData.added_by,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: taxData.added_by,
      };

      const addNotificationResult_user = await Admin.addNotification(notificationData_user);

      // Alert to all admins of new added product

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != taxData.added_by) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: taxData.added_by,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
    } else {
      reply.send({ status: 0, message: 'Failed to add Tax category' })
    }
  } catch (error) {
    console.log("addTaxCategory Error : ", error);
    reply.send({ status: 500, message: 'Error', addTaxCategoryError: error })
  }
}

const updateTaxCategory = async (request, reply) => {
  try {
    const { editId, userId, values } = request.body;
    let currentDate = Math.round(moment().valueOf() / 1000);
    const taxData = {
      name: values.name,
      value: values.taxValue,
      type: values.type?.id,
      added_by: userId,
      timestamp: currentDate,
      status: 1
    }
    const result = await Product.updateTaxCategory(editId, taxData);
    if (result) {
      // Notification
      let notificationMsg = `Tax category "${taxData.name}" has been updated.`;
      let notificationUrl = "tax_category";

      reply.send({ status: 1, message: notificationMsg })

      // Alert to user of new added product

      const incrementResult_User = await Admin.incrementField(taxData.added_by, "noti_count", 1);

      let notificationData_user = {
        user_id: taxData.added_by,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: taxData.added_by,
      };

      const addNotificationResult_user = await Admin.addNotification(notificationData_user);

      // Alert to all admins of new added product

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != taxData.added_by) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: taxData.added_by,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
    } else {
      reply.send({ status: 0, message: 'Failed to update Tax category' })
    }
  } catch (error) {
    console.log("updateTaxCategory Error : ", error);
    reply.send({ status: 500, message: 'Error', updateTaxCategoryError: error })
  }
}

const deleteTaxCategory = async (request, reply) => {
  try {
    const { deleteId, userId, status } = request.body;
    let currentDate = Math.round(moment().valueOf() / 1000);
    const newTaxData = {
      status: status
    }
    const result = await Product.updateTaxCategory(deleteId, newTaxData);
    if (result) {
      const taxData = await Product.getTaxCategoryById(deleteId);
      let notificationMsg = `Tax category "${taxData.name}" has been updated.`;
      if (status == 1) {
        notificationMsg = `Tax category "${taxData.name}" has been activated.`;
      } else if (status == 0) {
        notificationMsg = `Tax category "${taxData.name}" has been deactivated.`;
      } else if (status == 2) {
        notificationMsg = `Tax category "${taxData.name}" has been deleted.`;
      }
      reply.send({ status: 1, message: notificationMsg })
      // Notification
      let notificationUrl = "tax_category";

      // Alert to user of new added product

      const incrementResult_User = await Admin.incrementField(userId, "noti_count", 1);

      let notificationData_user = {
        user_id: userId,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: userId,
      };

      const addNotificationResult_user = await Admin.addNotification(notificationData_user);

      // Alert to all admins of new added product

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != userId) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
    } else {
      reply.send({ status: 0, message: `Failed to ${status == 1 ? 'activate' : (status == 0 ? 'deactivate' : 'delete')} Tax category` })
    }
  } catch (error) {
    console.log("deleteTaxCategory Error : ", error);
    reply.send({ status: 500, message: 'Error', deleteTaxCategoryError: error })
  }
}

//////////////////////////////////////////quickbook fun

const createQuickBooksInventoryAdjustment = async ({
  itemId,
  qtyDiff,
  productName = "",
  txnDate = new Date().toISOString().split("T")[0],
}) => {
  try {
    const token = await getNewAccessToken();
    let accountRefId = await findAccountIdByAcctNum(10134000);
    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

    if (!itemId) {
      console.error("Item ID is required for inventory adjustment.");
      return null;
    }

    if (!accountRefId) {
      console.error("Adjust account id is required for inventory adjustment.");
      return null;
    }

    if (Number(qtyDiff) === 0) {
      console.log("QtyDiff is 0, no inventory adjustment created.");
      return null;
    }


    const payload = {
      AdjustAccountRef: {
        value: String(accountRefId),
      },
      TxnDate: txnDate,
      PrivateNote: `${productName || "Item"} - Quantity adjustment`,
      Line: [
        {
          Description: productName || "",
          DetailType: "ItemAdjustmentLineDetail",
          ItemAdjustmentLineDetail: {
            ItemRef: {
              value: String(itemId),
            },
            QtyDiff: Number(qtyDiff),
          },
        },
      ],
    };

    console.log("Creating inventory adjustment payload:", payload);

    const response = await axios.post(
      `${apiUrl}/inventoryadjustment?minorversion=63`,
      payload,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
          Accept: "application/json",
        },
      }
    );

    console.log("Inventory adjustment created successfully:", response.data);
    return response.data;
  } catch (error) {
    if (error.response?.data) {
      console.error(
        "Error creating inventory adjustment:",
        JSON.stringify(error.response.data, null, 2)
      );

      error.response.data.Fault?.Error?.forEach((err) => {
        console.error(`Validation Error: ${err.Message}, Code: ${err.code}`);
      });
    } else {
      console.error("Error creating inventory adjustment:", error.message);
    }
    return null;
  }
};

const updateQuickBooksProductQuantity = async (productName, newQty, prevQty, quickbookId) => {
  try {
    // Get the access token
    let token = await getNewAccessToken();

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

    // Search for the product by name
    const searchResponse = await axios.get(
      `${apiUrl}/query?query=select * from Item where Name='${productName + locationInventoryCode}'&minorversion=63`,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          Accept: 'application/json',
        },
      }
    );

    const product = searchResponse.data.QueryResponse.Item?.[0];

    if (!product) {
      console.error(`Product with name "${productName}" not found in QuickBooks.`);
      return null;
    }

    console.log("Product found:", product);

    const adjustmentResult = await createQuickBooksInventoryAdjustment({
      itemId: product.Id,
      qtyDiff: Number(newQty),
      productName: product.Name,
    });
    // return true // new functionality in createQuickBooksInventoryAdjustment

    const currentQty = product.QtyOnHand || 0;
    const updatedQty = (Number(prevQty) || 0)
    // const updatedQty = (Number(prevQty) || 0) + Number(newQty);

    const quickBooksProductData = {
      Id: product.Id,
      SyncToken: product.SyncToken,
      Name: product.Name,
      QtyOnHand: updatedQty,
      // QtyDiff:Number(updatedQty) - Number(currentQty),
      InvStartDate: product.InvStartDate,
      TrackQtyOnHand: true,
      UnitPrice: product.UnitPrice,
      IncomeAccountRef: product.IncomeAccountRef,
      ExpenseAccountRef: product.ExpenseAccountRef,
      AssetAccountRef: product.AssetAccountRef,
      sparse: true,
    };

    console.log("Updating product quantity:", quickBooksProductData);

    // Update the product quantity
    const updateResponse = await axios.post(
      `${apiUrl}/item?operation=update&minorversion=63`,
      quickBooksProductData,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          Accept: 'application/json',
        },
      }
    );

    if (updateResponse.status === 200) {
      console.log("Product quantity updated successfully:", updateResponse.data);

      // Verify the updated quantity
      const verifyResponse = await axios.get(
        `${apiUrl}/item/${product.Id}?minorversion=63`,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
            Accept: 'application/json',
          },
        }
      );

      console.log("Re-fetched item after update:", verifyResponse.data);
      return verifyResponse.data;
    } else {
      console.error("Failed to update the product quantity:", updateResponse.data);
      return null;
    }
  } catch (error) {
    if (error.response?.data) {
      console.error("Error updating product quantity:", JSON.stringify(error.response.data, null, 2));

      error.response.data.Fault?.Error?.forEach((err) => {
        console.error(`Validation Error: ${err.Message}, Code: ${err.code}`);
      });
    } else {
      console.error("Error updating product quantity:", error.message);
    }
    return null;
  }
};

// const updateQuickBooksProductQuantityByQuickbookId = async (quickbookId, newQty, prevQty) => {
//   try {
//     // Get access token
//     const token = await getNewAccessToken();

//     const apiUrl = PRODUCTION
//       ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
//       : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

//     // Fetch the product directly by QuickBooks Id
//     const productResponse = await axios.get(
//       `${apiUrl}/item/${quickbookId}?minorversion=63`,
//       {
//         headers: {
//           Authorization: `Bearer ${token}`,
//           'Content-Type': 'application/json',
//           Accept: 'application/json',
//         },
//       }
//     );

//     const product = productResponse.data.Item;

//     if (!product) {
//       console.error(`Product with QuickBooks ID "${quickbookId}" not found.`);
//       return null;
//     }

//     console.log("Product found:", product);

//     // Calculate updated quantity
//     // (Decide logic: add, replace, or set manually)
//     const updatedQty = Number(prevQty) + Number(newQty); // or adjust as needed

//     const quickBooksProductData = {
//       Id: product.Id,
//       SyncToken: product.SyncToken,
//       Name: product.Name,
//       QtyOnHand: updatedQty,
//       InvStartDate: product.InvStartDate,
//       TrackQtyOnHand: true,
//       UnitPrice: product.UnitPrice,
//       IncomeAccountRef: product.IncomeAccountRef,
//       ExpenseAccountRef: product.ExpenseAccountRef,
//       AssetAccountRef: product.AssetAccountRef,
//       sparse: true,
//     };

//     console.log("Updating product quantity:", quickBooksProductData);

//     // Update product in QuickBooks
//     const updateResponse = await axios.post(
//       `${apiUrl}/item?operation=update&minorversion=63`,
//       quickBooksProductData,
//       {
//         headers: {
//           Authorization: `Bearer ${token}`,
//           'Content-Type': 'application/json',
//           Accept: 'application/json',
//         },
//       }
//     );

//     if (updateResponse.status === 200) {
//       console.log("Product quantity updated successfully:", updateResponse.data);

//       // Re-fetch to verify update
//       const verifyResponse = await axios.get(
//         `${apiUrl}/item/${product.Id}?minorversion=63`,
//         {
//           headers: {
//             Authorization: `Bearer ${token}`,
//             'Content-Type': 'application/json',
//             Accept: 'application/json',
//           },
//         }
//       );

//       console.log("Verified updated product:", verifyResponse.data);
//       return verifyResponse.data;
//     } else {
//       console.error("Failed to update product:", updateResponse.data);
//       return null;
//     }
//   } catch (error) {
//     if (error.response?.data) {
//       console.error("Error updating QuickBooks product:", JSON.stringify(error.response.data, null, 2));
//       error.response.data.Fault?.Error?.forEach((err) => {
//         console.error(`Validation Error: ${err.Message}, Code: ${err.code}`);
//       });
//     } else {
//       console.error("Error updating QuickBooks product:", error.message);
//     }
//     return null;
//   }
// };

const updateQuickBooksProductQuantityByQuickbookId = async (quickbookId, newQty, prevQty) => {
  try {
    const token = await getNewAccessToken();

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;



    // Fetch item
    const productResponse = await axios.get(
      `${apiUrl}/item/${quickbookId}?minorversion=63`,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
          Accept: "application/json",
        },
      }
    );

    const product = productResponse.data.Item;

    if (!product) {
      console.error(`Product with QuickBooks ID "${quickbookId}" not found.`);
      return null;
    }

    console.log("Product found:", product);

    const currentQty = Number(product.QtyOnHand || 0);
    const finalQty = Number(newQty || 0);

    // if newQty is final stock:
    const qtyDiff = finalQty //- currentQty;

    // if newQty itself is adjustment qty, then use:
    // const qtyDiff = Number(newQty || 0);

    console.log("currentQty:", currentQty);
    console.log("finalQty:", finalQty);
    console.log("qtyDiff:", qtyDiff);

    if (qtyDiff === 0) {
      console.log("No inventory adjustment needed.");
      return product;
    }

    const adjustmentResult = await createQuickBooksInventoryAdjustment({
      itemId: product.Id,
      qtyDiff,
      productName: product.Name,
    });

    if (!adjustmentResult) {
      console.error("Inventory adjustment failed.");
      return null;
    }

    // Verify updated item
    const verifyResponse = await axios.get(
      `${apiUrl}/item/${product.Id}?minorversion=63`,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
          Accept: "application/json",
        },
      }
    );

    console.log("Verified updated product:", verifyResponse.data);
    return verifyResponse.data;
  } catch (error) {
    if (error.response?.data) {
      console.error(
        "Error updating QuickBooks product:",
        JSON.stringify(error.response.data, null, 2)
      );

      error.response.data.Fault?.Error?.forEach((err) => {
        console.error(`Validation Error: ${err.Message}, Code: ${err.code}`);
      });
    } else {
      console.error("Error updating QuickBooks product:", error.message);
    }
    return null;
  }
};

// (async()=>{
//   const result = await updateQuickBooksProductQuantityByQuickbookId('5318',1,2);
//   console.log('result',result);
// })(); 

////////////////////////////////////////////

// const addFinishedProduct = async (req, res) => {
//   try {
//     // let barcode = crypto.randomBytes(7).toString("hex");
//     const id = crypto.randomBytes(6).readUIntLE(0, 6);
//     let barcode = String(id).padStart(14, '0');
//     let currentDate = Math.round(moment().valueOf() / 1000);
//     const addData = {};
//     addData['barcode'] = 'SW' + barcode;
//     addData['material_id'] = req.body.editId;
//     addData['unit_cost'] = req.body.values.unitCost;
//     addData['actual_qty'] = req.body.values.qty;
//     addData['qty'] = req.body.values.qty;
//     addData['actual_unit_qty'] = req.body.values.unit_qty;
//     addData['unit_qty'] = req.body.values.unit_qty;
//     addData['actual_total_cost'] = req.body.values.total;
//     addData['total_cost'] = req.body.values.total;
//     addData['status'] = 1;
//     addData['added_by'] = req.body.userId;
//     if (req.body.source) {
//       addData['source'] = req.body.source;
//     }
//     addData['timestamp'] = currentDate;


//     const result = await Product.addFinishedProduct(addData);
//     const materialData = await Product.getProductById(addData.material_id)
//     const updateData = {
//       price : materialData[0].max_price || 0
//     }
//     const updateMaterialPrice = await Product.updateProduct(addData.material_id, updateData)

//     // Update the quantity in QuickBooks
//     const updatedProduct = await updateQuickBooksProductQuantity(materialData[0].name, addData.actual_qty,materialData[0].total_qty);

//     if (updatedProduct) {
//       console.log("Quantity updated in QuickBooks successfully.");
//     } else {
//       console.error("Failed to update quantity in QuickBooks.");
//     }

//     if (result && materialData) {
//       // Notification

//       let notificationMsg = `Finished product "${materialData[0]?.name}" has been added.`;
//       let notificationUrl = "products";

//       const incrementResult_User = await Admin.incrementField(addData.added_by, "noti_count", 1);
//       let notificationData_user = {
//         user_id: addData.added_by,
//         url: notificationUrl,
//         timestamp: currentDate,
//         message: notificationMsg,
//         status: 1,
//         from_id: addData.added_by,
//       };
//       const addNotificationResult_user = await Admin.addNotification(notificationData_user);

//       let adminIds = await Admin.getAllAdminIds();
//       let notificationData = {};
//       for (const adminId of adminIds) {
//         if (adminId != addData.added_by) {
//           const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
//           notificationData = {
//             user_id: adminId,
//             url: notificationUrl,
//             timestamp: currentDate,
//             message: notificationMsg,
//             status: 1,
//             from_id: addData.added_by,
//           };
//           const addNotificationResult = await Admin.addNotification(notificationData);
//         }
//       }
//       res.send({ status: 1, message: notificationMsg })
//     }
//     else {
//       res.send({ status: 0, message: 'Failed to add finished product' })
//     }
//   }
//   catch (error) {
//     console.log("Error adding finished product : ", error);
//     res.send({ status: 500, message: 'Error adding finished product', error: error })
//   }
// }

const addFinishedProduct = async (req, res) => {
  try {
    // Build insert payload
    const id = crypto.randomBytes(6).readUIntLE(0, 6);
    const barcode = String(id).padStart(14, "0");
    const currentDate = Math.round(moment().valueOf() / 1000);

    const addData = {
      barcode: req.body.isCatalog ? "C" + barcode : "SW" + barcode,
      material_id: req.body.editId,
      unit_cost: req.body.values.unitCost,
      actual_qty: req.body.values.qty,
      qty: req.body.values.qty,
      actual_unit_qty: req.body.values.unit_qty,
      unit_qty: req.body.values.unit_qty,
      actual_total_cost: req.body.values.total,
      total_cost: req.body.values.total,
      status: 1,
      added_by: req.body.userId,
      timestamp: currentDate,
    };
    if (req.body.source) addData.source = req.body.source;

    // Insert finished product row
    const result = await Product.addFinishedProduct(addData);
    if (!result) {
      return res.send({ status: 0, message: "Failed to add finished product" });
    }

    // Fetch material
    const materialData = await Product.getProductById(addData.material_id);
    if (!materialData || !materialData.length) {
      return res.send({ status: 0, message: "Material not found" });
    }
    const material = materialData[0];

    // Sync material base price from max_price (fallback 0)
    const updateData = { price: material.max_price || 0 };
    await Product.updateProduct(addData.material_id, updateData);

    let message = `A finished product "${material.name}" has been added.`;
    await Product.addClientStockLog({
      catalog_id: req.body.editId,
      catalog_log_message: message,
      timestamp: currentDate,
      status: 1,
      catalog_source: 2,
      added_by: req.body.userId,
    });

    // Update quantity in QuickBooks (non-blocking)
    try {
      const updatedProduct = await updateQuickBooksProductQuantity(
        material.name,
        addData.actual_qty,
        material.total_qty
      );
      if (updatedProduct) {
        console.log("Quantity updated in QuickBooks successfully.");
      } else {
        console.error("Failed to update quantity in QuickBooks.");
      }
    } catch (qbErr) {
      console.error("QuickBooks quantity update error:", qbErr);
    }

    // === Reprice impacted recipes & catalogs (same calc used in updateProduct) ===
    try {
      const recipes = await Product.fetchRecipesBasedOnMaterial(addData.material_id);

      for (const r of recipes) {
        const recipeArr = await Recipe.getRecipeByIdForAutoUpdate(r.recipe_id);
        const recipe = recipeArr?.[0];
        if (!recipe) continue;

        // 1) Material costs (highest_unit_cost * quantity)
        const expectedMaterialCost = (recipe.ingredients || []).reduce((sum, ing) => {
          const qty = Number(ing?.quantity ?? 0);
          const unitCost = Number(ing?.highest_unit_cost ?? 0);
          return sum + unitCost * qty;
        }, 0);

        // 2) Subtotal + markup per ingredient
        const totals = (recipe.ingredients || []).reduce(
          (acc, ing) => {
            const qty = Number(ing?.quantity ?? 0);
            const unitCost = Number(ing?.highest_unit_cost ?? ing?.materialCost ?? 0); // safe fallback
            const markupPct = Number(ing?.markup ?? 0);
            const lineSub = unitCost * qty;
            const lineRetail = (lineSub * markupPct) / 100;
            acc.subTotal += lineSub;
            acc.retail += lineRetail;
            acc.total += lineSub + lineRetail; // total = sub + retail
            return acc;
          },
          { subTotal: 0, retail: 0, total: 0 }
        );

        const expectedMaterialCostWithMarkup = Number(totals.total.toFixed(2));

        // 3) Service cost (prefer expected_total_service_cost; fallback to sum of services)
        const expectedTotalServiceCost =
          Number(recipe?.expected_total_service_cost) ||
          (recipe?.services || []).reduce((sum, s) => {
            const amt = Number(s?.serviceAmount) || 0;
            const qty = Number(s?.quantity) || 0;
            return sum + amt * qty;
          }, 0);

        // 4) Update recipe totals
        await Recipe.updateRecipe(
          {
            expected_cost:
              Number(expectedMaterialCost) + Number(recipe?.expected_service_cost || 0),
            expected_material_cost: Number(expectedMaterialCost),
            expected_material_cost_with_markup: expectedMaterialCostWithMarkup,
            expected_cost_with_markup:
              Number(expectedMaterialCostWithMarkup) + Number(expectedTotalServiceCost),
          },
          recipe.id
        );

        // 5) Update products that use this recipe (wholesale/retail markup on material portion)
        // ---------- UPDATE PRODUCTS tied to this recipe (same as deleteFinishedProduct) ----------
        const allProductsWithCategory = await Product.fetchAllProductsByRecipe(true, false, recipe.id);
        for (let k = 0; k < allProductsWithCategory.length; k++) {
          const p = allProductsWithCategory[k];
          const emc = Number(p.expected_material_cost || 0);
          const ets = Number(p.expected_total_service_cost || 0);
          const cost = emc + ets;

          const productUpdate = {
            price: cost + (emc * (Number(p.wholesaleMarkup || 0)) / 100),
            // retail_price: cost + (emc * (Number(p.retailMarkup || 0)) / 100),
            special_price: emc,
            formula_cost: expectedMaterialCostWithMarkup + expectedTotalServiceCost,
          };

          await Product.updateProduct(p.id, productUpdate);
        }

        // // 6) Sync catalog products' special/formula with EMC-with-markup
        // const refreshed = await Recipe.getRecipeById(recipe.id);
        // const emcWithMarkup = Number(refreshed?.[0]?.expected_material_cost_with_markup) || 0;
        // console.log('emcWithMarkup**********', emcWithMarkup)

        // const catalogProducts = await Product.catalogByReceipte(recipe.id);
        // for (const cat of catalogProducts) {
        //   const prevSpecial = Number(cat.special_price || 0);
        //   console.log('prevSpecial-------------', prevSpecial)
        //   const prevFormula = Number(cat.formula_cost || 0);
        //   console.log('prevFormula--------------', prevFormula)
        //   const serviceCostPortion = prevFormula - prevSpecial; // keep service portion intact
        //   console.log('serviceCostPortion-----------', serviceCostPortion)

        //   await Product.updateProduct(cat.id, {
        //     special_price: emcWithMarkup,
        //     formula_cost: serviceCostPortion + emcWithMarkup,
        //   });
        // }
      }
    } catch (calcErr) {
      console.error("Post-add recipe recalculation error:", calcErr);
    }

    // Notifications
    const notificationMsg = `Finished product "${material?.name}" has been added.`;
    const notificationUrl = "products";

    await Admin.incrementField(addData.added_by, "noti_count", 1);
    await Admin.addNotification({
      user_id: addData.added_by,
      url: notificationUrl,
      timestamp: currentDate,
      message: notificationMsg,
      status: 1,
      from_id: addData.added_by,
    });

    const adminIds = await Admin.getAllAdminIds();
    for (const adminId of adminIds) {
      if (adminId != addData.added_by) {
        await Admin.incrementField(adminId, "noti_count", 1);
        await Admin.addNotification({
          user_id: adminId,
          url: notificationUrl,
          timestamp: currentDate,
          message: notificationMsg,
          status: 1,
          from_id: addData.added_by,
        });
      }
    }

    return res.send({ status: 1, message: notificationMsg });
  } catch (error) {
    console.log("Error adding finished product : ", error);
    return res.send({
      status: 500,
      message: "Error adding finished product",
      error,
    });
  }
}; //Deeraj's version with the product formula add

const getFinishedAndPartialProducts = async (req, res) => {
  try {
    const { editId } = req.body
    const result = await Product.getFinishedProducts(editId);
    if (result) {
      res.send({ status: 1, message: "Data fetched successfully", results: result })
    }
    else {
      res.send({ status: 0, message: "Error fetching data" })
    }
  }
  catch (error) {
    console.log("Error fetching finished products : ", error);
    reply.send({ status: 500, message: 'Error', error: error })
  }
}
const getFinishedProducts = async (req, res) => {
  try {
    const { editId } = req.body
    const result = await Product.getFinishedProducts(editId, false, notPartial = true);
    if (result) {
      res.send({ status: 1, message: "Data fetched successfully", results: result })
    }
    else {
      res.send({ status: 0, message: "Error fetching data" })
    }
  }
  catch (error) {
    console.log("Error fetching finished products : ", error);
    reply.send({ status: 500, message: 'Error', error: error })
  }
}
const getPartialFinishedProducts = async (req, res) => {
  try {
    const { editId } = req.body
    const result = await Product.getPartialFinishedProducts(editId);
    if (result) {
      res.send({ status: 1, message: "Data fetched successfully", results: result })
    }
    else {
      res.send({ status: 0, message: "Error fetching data" })
    }
  }
  catch (error) {
    console.log("Error fetching finished products : ", error);
    reply.send({ status: 500, message: 'Error', error: error })
  }
}
const getPartialFinishedProductsStockByUnit = async (req, res) => {
  try {
    const { editId } = req.body
    const result = await Product.getPartialFinishedProductsStockByUnit(editId);
    if (result) {
      res.send({ status: 1, message: "Data fetched successfully", results: result })
    }
    else {
      res.send({ status: 0, message: "Error fetching data" })
    }
  }
  catch (error) {
    console.log("Error fetching finished products : ", error);
    reply.send({ status: 500, message: 'Error', error: error })
  }
}
const getFinishedProductDataByID = async (req, res) => {
  const { id } = req.body
  let result = await Product.getFinishedProductsData(id)
  if (!empty(result)) {
    res.send({
      status: 1, data: result
    })
  } else {
    res.send({
      status: 0
    })
  }
}
const getAllFinishedProducts = async (req, res) => {
  try {
    const result = await Product.getFinishedProducts(0, false, notPartial = true);
    if (result) {
      res.send({ status: 1, message: "Data fetched successfully", results: result })
    }
    else {
      res.send({ status: 0, message: "Error fetching data" })
    }
  }
  catch (error) {
    console.log("Error fetching finished products : ", error);
    reply.send({ status: 500, message: 'Error', error: error })
  }
}

// const deleteFinishedProduct = async (req, res) => {
//   try {
//     const { id } = req.body;
//     const materialData = await Product.getFinishedProductById(id);
//     const results = await Product.deleteFinishedProduct(id)
//     const partialresults = await Product.deletePartialsOfFinishedProduct(id)
//     const productData = await Product.getProductById(materialData[0].material_id)
//     const updateData = {
//       price : productData[0].max_price || 0
//     }
//     const updateMaterialPrice = await Product.updateProduct(materialData[0].material_id, updateData);

//     if (results) {
//       res.send({ status: 1, message: 'Deleted successfully' })
//     }
//     else {
//       res.send({ status: 0, message: 'Failed to delete finished product' })
//     }
//   }
//   catch (error) {
//     console.log('Error deleting finished product', error)
//     res.send({ status: 500, message: 'Error deleting finished product', error: error })
//   }
// }

const deleteFinishedProduct = async (req, res) => {
  try {
    const { id } = req.body;
    let defaultMaterialExpectedCost = 0;
    // Find which material this finished-product belongs to
    const materialData = await Product.getFinishedProductById(id);
    console.log("materialData----------",materialData)
    let qbInvAdjResult =await createQuickBooksInventoryAdjustment({
      itemId: materialData[0].quickbookId,
      qtyDiff: materialData[0]?.qty * -1,
      productName: materialData[0]?.product_name,
  })
    if (!materialData || !materialData.length) {
      return res.send({ status: 0, message: 'Finished product not found' });
    }
    const materialId = materialData[0].material_id;

    // Delete main + partial entries
    const results = await Product.deleteFinishedProduct(id);
    await Product.deletePartialsOfFinishedProduct(id);

    // Sync the base material price (fallback to 0)
    try {
      const productData = await Product.getProductById(materialId);
      console.log('productData+++++++++++--', productData)
      defaultMaterialExpectedCost = productData[0]?.special_price
      const updateData = { price: (productData?.[0]?.max_price ?? 0) };
      await Product.updateProduct(materialId, updateData);
    } catch (syncErr) {
      console.error('Error syncing base material price:', syncErr);
      // do not fail the whole request because of this
    }

    // Recalculate all recipes that use this material (mirror updateProduct logic)
    try {
      const allRecipies = await Product.fetchRecipesBasedOnMaterial(materialId);
      console.log('allRecipies++++', allRecipies)

      for (let j = 0; j < allRecipies.length; j++) {
        // Use the same fetch as in updateProduct to get highest_unit_cost/markup/services
        const getRecipe = await Recipe.getRecipeByIdForAutoUpdate(allRecipies[j].recipe_id);
        const recipeRow = getRecipe?.[0];
        if (!recipeRow) continue;

        // Base expected material cost using highest_unit_cost * quantity
        console.log('recipeRow+++++', recipeRow?.ingredients)
        const expectedMaterialCost =
          recipeRow?.ingredients?.reduce(
            (acc, cur) => acc + (Number(cur?.highest_unit_cost || defaultMaterialExpectedCost || 0) * Number(cur?.quantity || 0)),
            0
          ) || defaultMaterialExpectedCost || 0;

        // NEW: compute with highest_unit_cost & markup (same as updateProduct)
        const totals = (recipeRow?.ingredients || []).reduce(
          (acc, ing) => {
            const qty = Number(ing?.quantity ?? 0);
            const unitCost = Number(ing?.highest_unit_cost ?? (ing?.materialCost || defaultMaterialExpectedCost) ?? 0);
            const markupPct = Number(ing?.markup ?? 0);

            const lineSub = unitCost * qty;             // Sub Total
            const lineRetail = (lineSub * markupPct) / 100; // Retail Amount
            acc.subTotal += lineSub;
            acc.retail += lineRetail;
            acc.total += lineSub + lineRetail;          // Total = Sub + Retail
            return acc;
          },
          { subTotal: 0, retail: 0, total: 0 }
        );

        const expectedMaterialCostWithMarkup = Number(totals.total.toFixed(2));

        // Service cost (match updateProduct logic)
        const expectedTotalServiceCost =
          Number(recipeRow?.expected_total_service_cost) ||
          (recipeRow?.services || []).reduce((sum, s) => {
            const amt = Number(s?.serviceAmount) || 0;
            const qty = Number(s?.quantity) || 0;
            return sum + (amt * qty);
          }, 0);

        // Keep original expected_cost calculation parity
        const expectedServiceCost = Number(recipeRow?.expected_service_cost) || 0;
        const expectedCost = expectedMaterialCost + expectedServiceCost;

        // Update recipe with new totals
        await Recipe.updateRecipe(
          {
            expected_cost: expectedCost,
            expected_material_cost: expectedMaterialCost,
            expected_material_cost_with_markup: expectedMaterialCostWithMarkup,
            expected_cost_with_markup: Number(expectedMaterialCostWithMarkup) + Number(expectedTotalServiceCost),
          },
          recipeRow.id
        );

        // ---------- UPDATE PRODUCTS tied to this recipe (same as deleteFinishedProduct) ----------
        const allProductsWithCategory = await Product.fetchAllProductsByRecipe(true, false, recipeRow.id);
        for (let k = 0; k < allProductsWithCategory.length; k++) {
          const p = allProductsWithCategory[k];
          const emc = Number(p.expected_material_cost || 0);
          const ets = Number(p.expected_total_service_cost || 0);
          const cost = emc + ets;

          const productUpdate = {
            price: cost + (emc * (Number(p.wholesaleMarkup || 0)) / 100),
            // retail_price: cost + (emc * (Number(p.retailMarkup || 0)) / 100),
            special_price: emc,
            formula_cost: expectedMaterialCostWithMarkup + expectedTotalServiceCost,
          };

          await Product.updateProduct(p.id, productUpdate);
        }

        // // Refresh recipe & push EMC with markup into catalog (same as updateProduct)
        // const refreshedRecipe = await Recipe.getRecipeById(recipeRow.id);
        // const emcWithMarkup = Number(refreshedRecipe?.[0]?.expected_material_cost_with_markup) || 0;

        // const catalogProducts = await Product.catalogByReceipte(recipeRow.id);
        // for (const cat of catalogProducts) {
        //   const prevSpecial = Number(cat.special_price || 0);
        //   const prevFormula = Number(cat.formula_cost || 0);
        //   const service_cost = prevFormula - prevSpecial; // preserve service delta

        //   const newSpecial = emcWithMarkup;
        //   const newFormula = service_cost + newSpecial;

        //   await Product.updateProduct(cat.id, {
        //     special_price: newSpecial,
        //     formula_cost: newFormula,
        //   });
        // }
      }
    } catch (recalcErr) {
      console.error('Error recalculating recipes after deletion:', recalcErr);
      // continue — don’t fail deletion because of recalculation error
    }

    if (results) {
      return res.send({ status: 1, message: 'Deleted successfully' });
    } else {
      return res.send({ status: 0, message: 'Failed to delete finished product' });
    }
  } catch (error) {
    console.log('Error deleting finished product', error);
    return res.send({ status: 500, message: 'Error deleting finished product', error });
  }
}; // Deeraj's version with the product formula added

const getFinishedProductById = async (req, res) => {
  try {
    const editId = req.body.editFinishedProductId
    const result = await Product.getFinishedProductById(editId)
    if (result) {
      res.send({ status: 1, message: 'Data fetched succesfully', results: result })
    }
    else {
      res.send({ status: 0, message: 'Failed to fetch data' })
    }
  }
  catch (error) {
    console.log(error);
    res.send({ status: 500, message: 'Error fetching data', error: error })
  }
}

const editFinishedProduct = async (req, res) => {
  try {
    let currentDate = Math.round(moment().valueOf() / 1000);
    const editId = req.body.editFinishedProductId
    const userId = req.body.userId
    const materialName = req.body.materialName
    let updateData = {};
    updateData['unit_cost'] = req.body.values.unitCost
    updateData['qty'] = req.body.values.qty
    updateData['total_cost'] = req.body.values.total
    const result = await Product.updateFinishedProduct(editId, updateData)
    const materialData = await Product.getFinishedProductById(editId);
    const productData = await Product.getProductById(materialData[0].material_id)
    const productUpdateData = {
      price: productData[0].max_price || 0
    }
    const updateMaterialPrice = await Product.updateProduct(materialData[0].material_id, productUpdateData);

    if (result && materialName) {
      // Notification
      let notificationMsg = `Finished product "${materialName}" has been updated.`;
      let notificationUrl = "products";

      const incrementResult_User = await Admin.incrementField(userId, "noti_count", 1);
      let notificationData_user = {
        user_id: userId,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: userId,
      };
      const addNotificationResult_user = await Admin.addNotification(notificationData_user);

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != userId) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
      res.send({ status: 1, message: notificationMsg })
    }
    else {
      res.send({ status: 0, message: 'Failed to update finished product' })
    }
  }
  catch (error) {
    console.log(error);
    res.send({ status: 500, message: 'Error updating finished product', error: error })
  }
}

// const moveFinishedProductToPartial = async (req, res) => {
//   try {
//     let currentDate = Math.round(moment().valueOf() / 1000);
//     const { userId,movingQty,convertionUnit,convertionQty,convertionUnitPrice,convertionTotalPrice } = req.body
//     const editId = req.body.editFinishedProductId
//     const autofill = req?.body?.autoFill === true ? 1 : 0;
//     const materialData = await Product.getFinishedProductById(editId);
//     if (!materialData || materialData.length === 0) {
//       return res.send({ status: 0, message: "Finished product not found" });
//     }
//     if (movingQty <= 0) {
//       return res.send({ status: 0, message: "Quantity must be greater than zero" });
//     }
//     if (movingQty > materialData[0]?.qty) {
//       return res.send({ status: 0, message: "Cannot move more than available quantity" });
//     }

//     if (!convertionQty || convertionQty <= 0) {
//       return res.send({ status: 0, message: "Conversion quantity must be valid" });
//     }
//     if ( convertionUnitPrice < 0) {
//       return res.send({ status: 0, message: "Conversion unit price must be valid" });
//     }

//     const newQty = materialData[0]?.qty - movingQty;
//     const newTotalCost = newQty * materialData[0]?.unit_cost;

//     let updateData = {
//       qty: newQty,
//       total_cost: newTotalCost
//     };
//     const updatedResult = await Product.updateFinishedProduct(editId, updateData);
//     const allPartialProducts = await Product.getPartialFinishedProducts(materialData[0]?.material_id);
//     console.log('autofill========',autofill);
//     console.log('materialData[0]========',materialData[0]);
//     console.log('allPartialProducts========',allPartialProducts);
//     let existingProduct = allPartialProducts?.find(product => {
//       return product.partial_unit === convertionUnit?.id &&
//           product.source === materialData[0]?.source &&
//           product.autofill === autofill &&
//           product.parent_finished_product_id === editId &&
//           (product.source !== 1 || product.vendor_id === materialData[0]?.vendor_id);
//     });
//     let result = '';
//     if (existingProduct) {
//       const materialUnitPrice = materialData[0]?.unit_cost;
//       const newPartialActualQty = existingProduct.actual_qty + convertionQty
//       const newPartialQty = existingProduct.qty + convertionQty
//       const newPartialUnitCost = materialUnitPrice / newPartialQty
//       let updateData = {
//           actual_qty: newPartialActualQty,
//           qty: newPartialQty,
//           unit_cost: newPartialUnitCost,
//           actual_total_cost: newPartialActualQty * newPartialUnitCost,
//           total_cost: newPartialQty * newPartialUnitCost
//       };
//       result = await Product.updateFinishedProduct(existingProduct.id, updateData);
//     } else {
//       const id = crypto.randomBytes(6).readUIntLE(0, 6);
//       let newBarcode = String(id).padStart(14, '0');
//       let addData = {};
//       addData['barcode'] = 'SW' + newBarcode;
//       addData['material_id'] = materialData[0]?.material_id;
//       addData['unit_cost'] = convertionUnitPrice;
//       addData['actual_qty'] = convertionQty;
//       addData['qty'] = convertionQty;
//       addData['actual_unit_qty'] = convertionQty;
//       addData['unit_qty'] = convertionQty;
//       addData['actual_total_cost'] = convertionTotalPrice;
//       addData['total_cost'] = convertionTotalPrice;
//       addData['status'] = 1;
//       addData['added_by'] = userId;
//       addData['source'] =  materialData[0]?.source || 0,
//       addData['timestamp'] = currentDate;
//       addData['receive_order_id'] =  materialData[0]?.receive_order_id || null,
//       addData['partial'] = 1
//       addData['partial_unit'] = convertionUnit?.id || null
//       addData['parent_finished_product_id'] = editId || null
//       addData['autofill'] = autofill || 0
//       result = await Product.addFinishedProduct(addData);
//     }

//     const materialName = materialData[0]?.product_name || ''
//     const barcode = materialData[0]?.barcode || ''
//     if (result) {
//       // Notification
//       let notificationMsg = `Finished product "${materialName}" "${barcode}" has been moved to partial.`;
//       let notificationUrl = "products";

//       const incrementResult_User = await Admin.incrementField(userId, "noti_count", 1);
//       let notificationData_user = {
//         user_id: userId,
//         url: notificationUrl,
//         timestamp: currentDate,
//         message: notificationMsg,
//         status: 1,
//         from_id: userId,
//       };
//       const addNotificationResult_user = await Admin.addNotification(notificationData_user);

//       let adminIds = await Admin.getAllAdminIds();
//       let notificationData = {};
//       for (const adminId of adminIds) {
//         if (adminId != userId) {
//           const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
//           notificationData = {
//             user_id: adminId,
//             url: notificationUrl,
//             timestamp: currentDate,
//             message: notificationMsg,
//             status: 1,
//             from_id: userId,
//           };
//           const addNotificationResult = await Admin.addNotification(notificationData);
//         }
//       }
//       res.send({ status: 1, message: notificationMsg })
//     }
//     else {
//       res.send({ status: 0, message: 'Failed to move finished product to partial' })
//     }
//   }
//   catch (error) {
//     console.log(error);
//     res.send({ status: 500, message: 'Error moving finished product to partial', error: error })
//   }
// }

const moveFinishedProductToPartial = async (req, res) => {
  try {
    let currentDate = Math.round(moment().valueOf() / 1000);
    const { userId, movingQty, convertionUnit, convertionQty, convertionUnitPrice, convertionTotalPrice } = req.body;
    const editId = req.body.editFinishedProductId;
    const autofill = req?.body?.autoFill === true ? 1 : 0;

    const materialData = await Product.getFinishedProductById(editId);
    if (!materialData || materialData.length === 0) {
      return res.send({ status: 0, message: "Finished product not found" });
    }
    if (movingQty <= 0) {
      return res.send({ status: 0, message: "Quantity must be greater than zero" });
    }
    if (movingQty > materialData[0]?.qty) {
      return res.send({ status: 0, message: "Cannot move more than available quantity" });
    }
    if (!convertionQty || convertionQty <= 0) {
      return res.send({ status: 0, message: "Conversion quantity must be valid" });
    }
    if (convertionUnitPrice < 0) {
      return res.send({ status: 0, message: "Conversion unit price must be valid" });
    }

    const newQty = materialData[0]?.qty - movingQty;
    const newTotalCost = newQty * materialData[0]?.unit_cost;

    await Product.updateFinishedProduct(editId, { qty: newQty, total_cost: newTotalCost });

    const allPartialProducts = await Product.getPartialFinishedProducts(materialData[0]?.material_id);

    let existingProduct = allPartialProducts?.find(product => {
      return product.partial_unit === convertionUnit?.id &&
        product.source === materialData[0]?.source &&
        product.autofill === autofill &&
        product.parent_finished_product_id === editId &&
        (product.source !== 1 || product.vendor_id === materialData[0]?.vendor_id);
    });

    let result = '';
    if (existingProduct) {
      const materialUnitPrice = materialData[0]?.unit_cost;
      const newPartialActualQty = existingProduct.actual_qty + convertionQty;
      const newPartialQty = existingProduct.qty + convertionQty;
      const newPartialUnitCost = materialUnitPrice / newPartialQty;

      let updateData = {
        actual_qty: newPartialActualQty,
        qty: newPartialQty,
        unit_cost: newPartialUnitCost,
        actual_total_cost: newPartialActualQty * newPartialUnitCost,
        total_cost: newPartialQty * newPartialUnitCost
      };
      result = await Product.updateFinishedProduct(existingProduct.id, updateData);
    } else {
      const id = crypto.randomBytes(6).readUIntLE(0, 6);
      let newBarcode = String(id).padStart(14, '0');

      let addData = {
        barcode: 'SW' + newBarcode,
        material_id: materialData[0]?.material_id,
        unit_cost: convertionUnitPrice,
        actual_qty: convertionQty,
        qty: convertionQty,
        actual_unit_qty: convertionQty,
        unit_qty: convertionQty,
        actual_total_cost: convertionTotalPrice,
        total_cost: convertionTotalPrice,
        status: 1,
        added_by: userId,
        source: materialData[0]?.source || 0,
        timestamp: currentDate,
        receive_order_id: materialData[0]?.receive_order_id || null,
        partial: 1,
        partial_unit: convertionUnit?.id || null,
        parent_finished_product_id: editId || null,
        autofill: autofill || 0
      };
      result = await Product.addFinishedProduct(addData);
    }

    const materialName = materialData[0]?.product_name || '';
    const barcode = materialData[0]?.barcode || '';

    if (result) {
      // ====================== FIXED RECALCULATION LOGIC (from addFinishedProduct) ======================
      const materialId = materialData[0]?.material_id;
      const recipes = await Product.fetchRecipesBasedOnMaterial(materialId);

      for (const r of recipes) {
        const recipeArr = await Recipe.getRecipeByIdForAutoUpdate(r.recipe_id);
        const recipe = recipeArr?.[0];
        if (!recipe) continue;

        // 1) Material costs (highest_unit_cost * quantity)
        const expectedMaterialCost = (recipe.ingredients || []).reduce((sum, ing) => {
          console.log('expectedMaterialCost--ing--', ing)
          const qty = Number(ing?.quantity ?? 0);
          const unitCost = Number(ing?.highest_unit_cost ?? 0);
          return sum + unitCost * qty;
        }, 0);

        // 2) Subtotal + markup per ingredient
        const totals = (recipe.ingredients || []).reduce(
          (acc, ing) => {
            const qty = Number(ing?.quantity ?? 0);
            const unitCost = Number(ing?.highest_unit_cost ?? ing?.materialCost ?? 0);
            const markupPct = Number(ing?.markup ?? 0);
            const lineSub = unitCost * qty;
            const lineRetail = (lineSub * markupPct) / 100;
            acc.subTotal += lineSub;
            acc.retail += lineRetail;
            acc.total += lineSub + lineRetail;
            return acc;
          },
          { subTotal: 0, retail: 0, total: 0 }
        );
        const expectedMaterialCostWithMarkup = Number(totals.total.toFixed(2));

        // 3) Service cost
        const expectedTotalServiceCost =
          Number(recipe?.expected_total_service_cost) ||
          (recipe?.services || []).reduce((sum, s) => {
            const amt = Number(s?.serviceAmount) || 0;
            const qty = Number(s?.quantity) || 0;
            return sum + amt * qty;
          }, 0);

        // 4) Update recipe
        await Recipe.updateRecipe(
          {
            expected_cost: Number(expectedMaterialCost) + Number(recipe?.expected_service_cost || 0),
            expected_material_cost: Number(expectedMaterialCost),
            expected_material_cost_with_markup: expectedMaterialCostWithMarkup,
            expected_cost_with_markup: Number(expectedMaterialCostWithMarkup) + Number(expectedTotalServiceCost),
          },
          recipe.id
        );

        // 5) Update products that use this recipe
        const allProductsWithCategory = await Product.fetchAllProductsByRecipe(true, false, recipe.id);
        for (let k = 0; k < allProductsWithCategory.length; k++) {
          const p = allProductsWithCategory[k];
          const emc = Number(p.expected_material_cost || 0);
          const ets = Number(p.expected_total_service_cost || 0);
          const cost = emc + ets;

          const productUpdate = {
            price: cost + (emc * (Number(p.wholesaleMarkup || 0)) / 100),
            // retail_price: cost + (emc * (Number(p.retailMarkup || 0)) / 100),
            special_price: emc,
            formula_cost: expectedMaterialCostWithMarkup + expectedTotalServiceCost,
          };
          await Product.updateProduct(p.id, productUpdate);
        }
      }
      // ====================== /FIXED RECALCULATION ======================

      // Notifications
      let notificationMsg = `Finished product "${materialName}" "${barcode}" has been moved to partial.`;
      let notificationUrl = "products";

      await Admin.incrementField(userId, "noti_count", 1);
      await Admin.addNotification({
        user_id: userId,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: userId,
      });

      let adminIds = await Admin.getAllAdminIds();
      for (const adminId of adminIds) {
        if (adminId != userId) {
          await Admin.incrementField(adminId, "noti_count", 1);
          await Admin.addNotification({
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          });
        }
      }

///////////////////qb stock update logic///////////////////
    // const materialInventoryData = await Product.getMaterialInventoryByMaterialId(materialData[0].material_id)

    // let totalStock = 0;
    //    if (materialInventoryData) {
    //       totalStock = sumQuantities(materialInventoryData); //meed both finished and partial stock
    //     }
    //     console.log("totalStocktotalStock",totalStock)
    //     const qbData={
    //       unit_qty :Number(totalStock) || 0,
    //       quickbookId:materialData[0]?.quickbookId
    //     }
    //     let updateStockInqb = await updateInvStockInQuickBooks(qbData)
        //////////////////////////////////

      return res.send({ status: 1, message: notificationMsg });
    } else {
      return res.send({ status: 0, message: 'Failed to move finished product to partial' });
    }
  } catch (error) {
    console.log(error);
    return res.send({ status: 500, message: 'Error moving finished product to partial', error });
  }
};

const moveFinishedProductToWaste = async (req, res) => {
  try {
    let currentDate = Math.round(moment().valueOf() / 1000);
    const editId = req.body.editFinishedProductId
    const userId = req.body.userId
    const wasteqty = req.body.wasteqty
    const materialData = await Product.getFinishedProductById(editId);
    if (!materialData || materialData.length === 0) {
      console.log("No material found for this ID.");
      return;
    }
    console.log('materialDatamaterialData', materialData)

    const materialName = materialData[0]?.product_name || ''
    const barcode = materialData[0]?.barcode || ''
    const qty = wasteqty   //materialData[0]?.qty ? `Qty ${materialData[0]?.qty} of ` : ''
    const { id, product_name, vendor_id, ...insertData } = materialData[0];
    const addDeletedMaterialInventory = await Product.addDeletedMaterialInventory(insertData)
    let result = false
    let balanceQty = parseFloat(materialData[0].qty - wasteqty).toFixed(2)
    let action = null
    if (balanceQty == 0 || balanceQty < 0) {
      action = 1
      const parentFinishedMaterial = await Product.getMaterialById(materialData[0]?.parent_finished_product_id)
      console.log('parentFinishedMaterialparentFinishedMaterial', parentFinishedMaterial)
      if (parentFinishedMaterial.length > 0 && parentFinishedMaterial[0]?.qty > 0) {
        let reducedQty = parentFinishedMaterial[0]?.qty - 1
        let parentTotalCost = parentFinishedMaterial[0]?.unit_cost * reducedQty
        let parentData = {
          qty: reducedQty,
          total_cost: parseInt(parentTotalCost)
        };
        const reduceFinishedProductStock = await Product.updateMaterialStock(materialData[0]?.parent_finished_product_id, parentData)
        console.log('reduceFinishedProductStock', reduceFinishedProductStock)
        let addingQty = materialData[0]?.actual_qty
        let totalCost = materialData[0]?.actual_total_cost
        let autoFilldata = {
          qty: addingQty,
          total_cost: totalCost
        };
        result = await Product.updateMaterialStock(materialData[0]?.id, autoFilldata);
        console.log('autofillPartialStock--', result);
      } else {
        action = 3
        const updatedData = {
          qty: 0
        }
        result = await Product.updateFinishedProduct(editId, updatedData)
        // result = await Product.deleteFinishedProduct(editId)
      }
    } else {
      const updatedData = {
        qty: balanceQty
      }
      action = 2
      result = await Product.updateFinishedProduct(editId, updatedData)
      // const result = await Product.deleteFinishedProduct(editId)
    }
    if (result) {
      // Notification

      let notificationMsg = `${qty} Finished product ${materialName} "${barcode}" has been moved to waste.`;
      let notificationUrl = "products";

      if (action == 1) {
        notificationMsg = `${qty} Partial Finished product ${materialName} "${barcode}" has been moved to waste.`;

      } else if (action == 2) {
        notificationMsg = `${qty} Partial Finished product ${materialName} "${barcode}" has been moved to waste.`;
      } else {
        notificationMsg = `${qty} Finished product ${materialName} "${barcode}" has been moved to waste.`;
      }

      const incrementResult_User = await Admin.incrementField(userId, "noti_count", 1);
      let notificationData_user = {
        user_id: userId,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: userId,
      };
      const addNotificationResult_user = await Admin.addNotification(notificationData_user);

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != userId) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
///////////////////////////////////////qb stock update logic///////////////////
    //   const materialInventoryData = await Product.getMaterialInventoryByMaterialId(materialData[0].material_id)

    // let totalStock = 0;
    //    if (materialInventoryData) {
    //       totalStock = sumQuantities(materialInventoryData); //meed both finished and partial stock
    //     }
    //     console.log("totalStocktotalStock",totalStock)
    //     const qbData={
    //       unit_qty :Number(totalStock) || 0,
    //       quickbookId:materialData[0]?.quickbookId
    //     }
    //     let updateStockInqb = await updateInvStockInQuickBooks(qbData)
//////////////////////////////////////////////
      res.send({ status: 1, message: notificationMsg })
    }
    else {
      res.send({ status: 0, message: 'Failed to move finished product to waste' })
    }
  }
  catch (error) {
    console.log(error);
    res.send({ status: 500, message: 'Error moving finished product to waste', error: error })
  }
}

// const moveFinishedProductToWaste = async (req, res) => { 
//   try {
//     let currentDate = Math.round(moment().valueOf() / 1000);
//     const editId   = req.body.editFinishedProductId;
//     const userId   = req.body.userId;
//     const wasteqty = Number(req.body.wasteqty || 0);

//     const materialData = await Product.getFinishedProductById(editId);
//     if (!materialData || materialData.length === 0) {
//       return res.send({ status: 0, message: "Finished product not found" });
//     }

//     const material = materialData[0];
//     const materialName = material?.product_name || '';
//     const barcode      = material?.barcode || '';

//     // Safety checks
//     if (wasteqty <= 0) {
//       return res.send({ status: 0, message: "Waste quantity must be greater than zero" });
//     }
//     if (wasteqty > Number(material?.qty || 0)) {
//       return res.send({ status: 0, message: "Cannot waste more than available quantity" });
//     }

//     // Keep a record in deleted material inventory (minus some fields)
//     const { id, product_name, vendor_id, ...insertData } = material;
//     await Product.addDeletedMaterialInventory(insertData);

//     let result = false;
//     let action = null; // 1: zeroed & parent adjust; 2: partial reduction; 3: zeroed w/o parent
//     const balanceQty = Number((Number(material.qty) - wasteqty).toFixed(2));

//     if (balanceQty <= 0) {
//       action = 1;

//       const parentFinishedMaterial = await Product.getMaterialById(material?.parent_finished_product_id);
//       if (parentFinishedMaterial.length > 0 && Number(parentFinishedMaterial[0]?.qty) > 0) {
//         // Reduce one from parent finished product
//         const reducedQty     = Number(parentFinishedMaterial[0]?.qty) - 1;
//         const parentUnitCost = Number(parentFinishedMaterial[0]?.unit_cost || 0);
//         const parentData = {
//           qty: reducedQty,
//           total_cost: parseInt(parentUnitCost * reducedQty, 10)
//         };
//         await Product.updateMaterialStock(material?.parent_finished_product_id, parentData);

//         // Refill this partial item back to its full "actual" values
//         const autoFilldata = {
//           qty:        Number(material?.actual_qty || 0),
//           total_cost: Number(material?.actual_total_cost || 0)
//         };
//         result = await Product.updateMaterialStock(material?.id, autoFilldata);
//       } else {
//         // No parent to decrement; just zero this item
//         action = 3;
//         result = await Product.updateFinishedProduct(editId, { qty: 0 });
//       }
//     } else {
//       // Just reduce this item quantity
//       action = 2;
//       result = await Product.updateFinishedProduct(editId, { qty: balanceQty });
//     }

//     if (!result) {
//       return res.send({ status: 0, message: "Failed to move finished product to waste" });
//     }

//     // ====================== INTEGRATED: Recalculate recipes & catalog based on this material ======================
//     try {
//       const materialId  = material?.material_id;
//       const allRecipies = await Product.fetchRecipesBasedOnMaterial(materialId);

//       for (let j = 0; j < allRecipies.length; j++) {
//         const getRecipe = await Recipe.getRecipeByIdForAutoUpdate(allRecipies[j].recipe_id);

//         let finishedCost = 0;
//         let partialCost  = 0;

//         (getRecipe[0]?.ingredients || []).forEach((ing) => {
//           const qty = Number(ing?.quantity ?? 0);
//           const st  = Number(ing?.stockType);
//           if (st === 1) {
//             finishedCost += Number(ing?.highest_finished_unit_cost ?? 0) * qty;
//           } else if (st === 2) {
//             partialCost  += Number(ing?.highest_partial_unit_cost  ?? 0) * qty;
//           }
//         });

//         const expectedMaterialCost = finishedCost + partialCost;

//         // Markup-inclusive expected material cost (kept same as your partial-move logic)
//         const totals = (getRecipe[0]?.ingredients || []).reduce(
//           (acc, ing) => {
//             const qty       = Number(ing?.quantity ?? 0);
//             const unitCost  = Number(ing?.highest_unit_cost ?? ing?.materialCost ?? 0);
//             const markupPct = Number(ing?.markup ?? 0);

//             const lineSub    = unitCost * qty;
//             const lineRetail = (lineSub * markupPct) / 100;
//             acc.subTotal += lineSub;
//             acc.retail   += lineRetail;
//             acc.total    += lineSub + lineRetail;
//             return acc;
//           },
//           { subTotal: 0, retail: 0, total: 0 }
//         );

//         const expectedMaterialCostWithMarkup = Number(totals.total.toFixed(2));

//         // Service cost (fallback to recompute from services if not present)
//         const expectedTotalServiceCost =
//           Number(getRecipe?.[0]?.expected_total_service_cost) ||
//           (getRecipe?.[0]?.services || []).reduce((sum, s) => {
//             const amt = Number(s?.serviceAmount) || 0;
//             const qty = Number(s?.quantity) || 0;
//             return sum + amt * qty;
//           }, 0);

//         const expectedCost = expectedMaterialCost + Number(getRecipe[0]?.expected_service_cost || 0);

//         const recipeUpdate = {
//           expected_cost: expectedCost,
//           expected_material_cost: expectedMaterialCost,
//           expected_material_cost_with_markup: expectedMaterialCostWithMarkup,
//           expected_cost_with_markup: Number(expectedMaterialCostWithMarkup) + Number(expectedTotalServiceCost),
//         };
//         await Recipe.updateRecipe(recipeUpdate, getRecipe[0].id);

//         // Update products linked to this recipe (same as your partial flow)
//         const allProductsWithCategory = await Product.fetchAllProductsByRecipe(true, false, getRecipe[0].id);
//         for (let k = 0; k < allProductsWithCategory.length; k++) {
//           const row   = allProductsWithCategory[k];
//           const cost  = Number(row.expected_material_cost || 0) + Number(row.expected_total_service_cost || 0);

//           const product = {};
//           product.price         = cost + (Number(row.expected_material_cost || 0) * Number(row.wholesaleMarkup || 0) / 100);
//           product.retail_price  = cost + (Number(row.expected_material_cost || 0) * Number(row.retailMarkup   || 0) / 100);
//           product.special_price = Number(row.expected_material_cost || 0);

//           await Product.updateProduct(row.id, product);
//         }

//         // Refresh recipe and propagate to catalog
//         const refreshedRecipe = await Recipe.getRecipeById(getRecipe[0].id);
//         const emcWithMarkup   = Number(refreshedRecipe?.[0]?.expected_material_cost_with_markup) || 0;

//         const catalogProducts = await Product.catalogByReceipte(getRecipe[0].id);
//         for (const cat of catalogProducts) {
//           const prevSpecial  = Number(cat.special_price || 0);
//           const prevFormula  = Number(cat.formula_cost  || 0);
//           const service_cost = prevFormula - prevSpecial;

//           const newSpecial = emcWithMarkup;
//           const newFormula = service_cost + newSpecial;

//           await Product.updateProduct(cat.id, {
//             special_price: newSpecial,
//             formula_cost:  newFormula,
//           });
//         }
//       }
//     } catch (recalcErr) {
//       // Log but don't fail the whole waste operation
//       console.error("Recalculation error after waste:", recalcErr);
//     }
//     // ====================== /INTEGRATED ======================

//     // Notifications
//     let notificationMsg = `${wasteqty} Finished product ${materialName} "${barcode}" has been moved to waste.`;
//     const notificationUrl = "products";

//     if (action === 1) {
//       notificationMsg = `${wasteqty} Partial Finished product ${materialName} "${barcode}" has been moved to waste.`;
//     } else if (action === 2) {
//       notificationMsg = `${wasteqty} Partial Finished product ${materialName} "${barcode}" has been moved to waste.`;
//     } else {
//       notificationMsg = `${wasteqty} Finished product ${materialName} "${barcode}" has been moved to waste.`;
//     }

//     await Admin.incrementField(userId, "noti_count", 1);
//     await Admin.addNotification({
//       user_id: userId,
//       url: notificationUrl,
//       timestamp: currentDate,
//       message: notificationMsg,
//       status: 1,
//       from_id: userId,
//     });

//     const adminIds = await Admin.getAllAdminIds();
//     for (const adminId of adminIds) {
//       if (adminId != userId) {
//         await Admin.incrementField(adminId, "noti_count", 1);
//         await Admin.addNotification({
//           user_id: adminId,
//           url: notificationUrl,
//           timestamp: currentDate,
//           message: notificationMsg,
//           status: 1,
//           from_id: userId,
//         });
//       }
//     }

//     return res.send({ status: 1, message: notificationMsg });

//   } catch (error) {
//     console.log(error);
//     return res.send({ status: 500, message: 'Error moving finished product to waste', error });
//   }
// }; // Deeraj's version with the product formula adding

const deletePartialFinishedProduct = async (req, res) => {
  try {
    let currentDate = Math.round(moment().valueOf() / 1000);
    const editId = req.body.deleteFinishedProductId
    const userId = req.body.userId
    const materialData = await Product.getFinishedProductById(editId);
    if (!materialData || materialData.length === 0) {
      console.log("No material found for this ID.");
      return;
    }
    const materialName = materialData[0]?.product_name || ''
    const barcode = materialData[0]?.barcode || ''
    const qty = materialData[0]?.qty ? `with quantity ${materialData[0]?.qty}` : ''
    const { id, product_name, vendor_id, ...insertData } = materialData[0];
    const addDeletedMaterialInventory = await Product.addDeletedMaterialInventory(insertData)
    const result = await Product.deleteFinishedProduct(editId)

    if (result) {
      // Notification
      let notificationMsg = `Partial Finished Product '${materialName}' (${barcode}) has been deleted ${qty}`;
      let notificationUrl = "products";

      const incrementResult_User = await Admin.incrementField(userId, "noti_count", 1);
      let notificationData_user = {
        user_id: userId,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: userId,
      };
      const addNotificationResult_user = await Admin.addNotification(notificationData_user);

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != userId) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
      res.send({ status: 1, message: notificationMsg })
    }
    else {
      res.send({ status: 0, message: 'Failed to delete partial finished product' })
    }
  } catch (error) {
    console.log(error);
    res.send({ status: 500, message: 'Error deleting partial finished product', error: error })
  }
}

const refillFinishedProduct = async (req, res) => {
  try {
    let currentDate = Math.round(moment().valueOf() / 1000);
    const { refillQty, refillFinishedProductId, userId } = req.body
    const materialData = await Product.getFinishedProductById(refillFinishedProductId);
    if (!materialData || materialData.length === 0) {
      console.log("No material found for this ID.");
      return;
    }
    const unitCost = materialData[0]?.unit_cost || 0
    const currentQty = materialData[0]?.qty || 0;
    const newQty = parseFloat(currentQty) + parseFloat(refillQty);
    const newTotal = newQty * unitCost
    const materialName = materialData[0]?.product_name || ''
    const editBarcode = materialData[0]?.barcode || ''

    let updateData = {};
    updateData['unit_cost'] = unitCost
    updateData['qty'] = newQty
    updateData['total_cost'] = newTotal
    const result = await Product.updateFinishedProduct(refillFinishedProductId, updateData);

    const qbResult = await updateQuickBooksProductQuantity(materialName, refillQty, currentQty, materialData[0]?.quickbookId);
    if (qbResult) {
      console.log("Quantity updated in QuickBooks successfully.");
    } else {
      console.error("Failed to update quantity in QuickBooks.");
    }

    if (result && materialName) {
      // Notification
      let notificationMsg = `Finished product has been refilled for "${materialName} (${editBarcode})".`;
      let notificationUrl = "products";

      const incrementResult_User = await Admin.incrementField(userId, "noti_count", 1);
      let notificationData_user = {
        user_id: userId,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: userId,
      };
      const addNotificationResult_user = await Admin.addNotification(notificationData_user);

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != userId) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
      res.send({ status: 1, message: notificationMsg })
    }
    else {
      res.send({ status: 0, message: 'Failed to refill finished product' })
    }
  }
  catch (error) {
    console.log(error);
    res.send({ status: 500, message: 'Error refilling finished product', error: error })
  }
}

const updateInventory = async (req, res) => {
  try {
    let currentDate = Math.round(moment().valueOf() / 1000);
    const { editId, editFinishedProductId } = req.body
    const { materialName, editBarcode, userId } = req.body
    let updateData = {};
    updateData['unit_cost'] = req.body.values.unitCost
    updateData['qty'] = req.body.values.qty
    updateData['total_cost'] = req.body.values.total
    const result = await Product.updateFinishedProduct(editFinishedProductId, updateData)
    let inventoryLogData = {};
    inventoryLogData['material_inventory_id'] = editFinishedProductId
    inventoryLogData['material_id'] = editId
    inventoryLogData['reason'] = req.body.values.inventoryReason
    inventoryLogData['added_by'] = userId
    inventoryLogData['timestamp'] = currentDate
    const logData = await Product.addInventoryAuditLog(inventoryLogData)
    if (result && materialName && logData) {
      // Fetch material
      const materialData = await Product.getProductById(editId);
      if (!materialData || !materialData.length) {
        return res.send({ status: 0, message: "Material not found" });
      }
      const material = materialData[0];

      // Sync material base price from max_price (fallback 0)
      const materialUpdateData = { price: material.max_price || 0 };
      await Product.updateProduct(editId, materialUpdateData);

      // Update quantity in QuickBooks (non-blocking)
      try {
        const updatedProduct = await updateQuickBooksProductQuantity(
          material.name,
          updateData.qty,
          material.total_qty
        );
        if (updatedProduct) {
          console.log("Quantity updated in QuickBooks successfully.");
        } else {
          console.error("Failed to update quantity in QuickBooks.");
        }
      } catch (qbErr) {
        console.error("QuickBooks quantity update error:", qbErr);
      }

      // === Reprice impacted recipes & catalogs (same calc used in updateProduct) ===
      try {
        const recipes = await Product.fetchRecipesBasedOnMaterial(editId);

        for (const r of recipes) {
          const recipeArr = await Recipe.getRecipeByIdForAutoUpdate(r.recipe_id);
          const recipe = recipeArr?.[0];
          if (!recipe) continue;

          // 1) Material costs (highest_unit_cost * quantity)
          const expectedMaterialCost = (recipe.ingredients || []).reduce((sum, ing) => {
            const qty = Number(ing?.quantity ?? 0);
            const unitCost = Number(ing?.highest_unit_cost ?? 0);
            return sum + unitCost * qty;
          }, 0);

          // 2) Subtotal + markup per ingredient
          const totals = (recipe.ingredients || []).reduce(
            (acc, ing) => {
              const qty = Number(ing?.quantity ?? 0);
              const unitCost = Number(ing?.highest_unit_cost ?? ing?.materialCost ?? 0); // safe fallback
              const markupPct = Number(ing?.markup ?? 0);
              const lineSub = unitCost * qty;
              const lineRetail = (lineSub * markupPct) / 100;
              acc.subTotal += lineSub;
              acc.retail += lineRetail;
              acc.total += lineSub + lineRetail; // total = sub + retail
              return acc;
            },
            { subTotal: 0, retail: 0, total: 0 }
          );

          const expectedMaterialCostWithMarkup = Number(totals.total.toFixed(2));

          // 3) Service cost (prefer expected_total_service_cost; fallback to sum of services)
          const expectedTotalServiceCost =
            Number(recipe?.expected_total_service_cost) ||
            (recipe?.services || []).reduce((sum, s) => {
              const amt = Number(s?.serviceAmount) || 0;
              const qty = Number(s?.quantity) || 0;
              return sum + amt * qty;
            }, 0);

          // 4) Update recipe totals
          await Recipe.updateRecipe(
            {
              expected_cost:
                Number(expectedMaterialCost) + Number(recipe?.expected_service_cost || 0),
              expected_material_cost: Number(expectedMaterialCost),
              expected_material_cost_with_markup: expectedMaterialCostWithMarkup,
              expected_cost_with_markup:
                Number(expectedMaterialCostWithMarkup) + Number(expectedTotalServiceCost),
            },
            recipe.id
          );

          // 5) Update products that use this recipe (wholesale/retail markup on material portion)
          const allProductsWithCategory = await Product.fetchAllProductsByRecipe(true, false, recipe.id);
          for (let k = 0; k < allProductsWithCategory.length; k++) {
            const p = allProductsWithCategory[k];
            const emc = Number(p.expected_material_cost || 0);
            const ets = Number(p.expected_total_service_cost || 0);
            const cost = emc + ets;

            const productUpdate = {
              price: cost + (emc * (Number(p.wholesaleMarkup || 0)) / 100),
              // retail_price: cost + (emc * (Number(p.retailMarkup || 0)) / 100),
              special_price: emc,
              formula_cost: expectedMaterialCostWithMarkup + expectedTotalServiceCost,
            };

            await Product.updateProduct(p.id, productUpdate);
          }
        }
      } catch (calcErr) {
        console.error("Post-update recipe recalculation error:", calcErr);
      }

      // Notification
      let notificationMsg = `Inventory for "${materialName} (${editBarcode})" has been updated.`;
      let notificationUrl = "products";

      const incrementResult_User = await Admin.incrementField(userId, "noti_count", 1);
      let notificationData_user = {
        user_id: userId,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: userId,
      };
      const addNotificationResult_user = await Admin.addNotification(notificationData_user);

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != userId) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
      res.send({ status: 1, message: notificationMsg })
    }
    else {
      res.send({ status: 0, message: 'Failed to update inventory' })
    }
  }
  catch (error) {
    console.log(error);
    res.send({ status: 500, message: 'Error updating inventory', error: error })
  }
}

const fetchInventoryAuditLog = async (req, res) => {
  try {
    const { editId } = req.body
    const result = await Product.fetchInventoryAuditLog(editId)
    if (result) {
      res.send({ status: 1, message: 'Inventory logs fetched successfully', logData: result })
    }
    else {
      res.send({ status: 0, message: 'Failed to update inventory' })
    }
  }
  catch (error) {
    console.log(error);
    res.send({ status: 500, message: 'Error updating inventory', error: error })
  }
}

const CreateReceiveOrder = async (req, res) => {
  try {
    let currentDate = moment().unix();
    let receivedDate = req.body.values.receivedDate ? moment(req.body.values.receivedDate).unix() : null;
    let expectedDate = req.body.values.expectedDate ? moment(req.body.values.expectedDate).unix() : null;
    const orderData = {};
    orderData['name'] = req.body.values.name;
    orderData['vendor_id'] = req.body.values.vendor;
    orderData['transaction_id'] = req.body.values.transactionId;
    orderData['received_date'] = receivedDate;
    orderData['expected_date'] = expectedDate;
    orderData['include_tax'] = req.body.values.includeTax;
    orderData['added_by'] = req.body.userId;
    orderData['timestamp'] = currentDate;
    orderData['sub_total'] = req.body.values.sub_total || 0;
    orderData['tax'] = req.body.values.tax;
    orderData['grand_total'] = req.body.values.grand_total || 0;
    orderData['tax_category'] = req.body.values.taxCategory || 0;
    orderData['orderType'] = req.body.values.orderType == "stockCatalog" ? 2 : req.body.values.orderType == "serviceOnly" ? 3 : 1;
    orderData['project_type'] = req?.body?.values?.projectType?.id || null;
    orderData['ship_via'] = req.body.values.shipVia || null;
    orderData['notes'] = req.body.values.notes || null;
    orderData['sales_order_id'] = req?.body?.values?.salesOrderId || null;

    const orderResult = await Product.CreateReceiveOrder(orderData)
    let result, materialData;
    if (orderResult) {
      const products = req.body.values.products;
      const catalogs = req.body.values.stockCatalogs;

      let combinedProducts = [...products, ...catalogs];
      for (const row of combinedProducts) {
        console.log('row productsssssss', row)
        const qty = row.quantity ? row.quantity : 0;
        const cost = row.unitCost ? row.unitCost : 0;
        const shipping_cost = row.shipping_cost ? row.shipping_cost : 0;
        const service_cost = Number(row.services?.total_cost) || 0;
        const service_id = row.services?.id || null;
        // let barcode = crypto.randomBytes(7).toString("hex");
        const id = crypto.randomBytes(6).readUIntLE(0, 6);
        let barcode = String(id).padStart(14, '0');
        const addData = {};
        addData['barcode'] = row?.meterial == 1 ? 'SW' + barcode : 'C' + barcode;
        addData['material_id'] = row.id;
        addData['unit_cost'] = parseFloat(cost);
        addData['actual_qty'] = parseFloat(qty);
        addData['qty'] = parseFloat(qty);
        // addData['actual_total_cost'] = parseFloat(cost * qty);
        // addData['total_cost'] = parseFloat(cost * qty);
        addData['actual_total_cost'] = parseFloat((cost * qty) + (shipping_cost * qty) + service_cost);
        addData['total_cost'] = parseFloat((cost * qty) + (shipping_cost * qty) + service_cost);
        addData['status'] = 1;
        addData['added_by'] = req.body.userId;
        addData['source'] = 1;
        addData['timestamp'] = currentDate;
        addData['receive_order_id'] = orderResult;
        addData['shipping_cost'] = parseFloat(shipping_cost);
        addData['service_cost'] = parseFloat(service_cost);
        addData['service_id'] = parseFloat(service_id) || null;

        result = await Product.addFinishedProduct(addData);

        // Update last ordered date for the material - updates every time material is successfully added to purchase order
        if (result) {
          try {
            // Recalculate current timestamp fresh each time to ensure we always get the current time
            // This prevents using stale timestamps and ensures each order gets the correct date
            const orderTimestamp = moment().unix(); // Current Unix timestamp in seconds

            const updateResult = await Product.updateProduct(addData.material_id, { last_ordered_date: orderTimestamp });
            // Sequelize update returns [affectedRows], so check if any rows were updated
            if (updateResult && Array.isArray(updateResult) && updateResult[0] > 0) {
              const formattedDate = moment.unix(orderTimestamp).format('YYYY-MM-DD HH:mm:ss');
              console.log(`Successfully updated last_ordered_date for material_id: ${addData.material_id} to timestamp: ${orderTimestamp} (${formattedDate})`);
            } else {
              console.error(`Failed to update last_ordered_date for material_id: ${addData.material_id}. Update result:`, updateResult);
            }
          } catch (updateError) {
            console.error(`Error updating last_ordered_date for material_id ${addData.material_id}:`, updateError);
          }
        }

        materialData = await Product.getProductById(addData.material_id)

        if (result && materialData) {


          // Notification

          let notificationMsg = `Order "${orderData.name}" created for ${row?.meterial == 1 ? "material" : "catalog"} "${materialData[0]?.name}"`;
          let notificationUrl = "receive_inventory";

          const incrementResult_User = await Admin.incrementField(addData.added_by, "noti_count", 1);
          let notificationData_user = {
            user_id: addData.added_by,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: addData.added_by,
          };
          const addNotificationResult_user = await Admin.addNotification(notificationData_user);

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};
          for (const adminId of adminIds) {
            if (adminId != addData.added_by) {
              const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
              notificationData = {
                user_id: adminId,
                url: notificationUrl,
                timestamp: currentDate,
                message: notificationMsg,
                status: 1,
                from_id: addData.added_by,
              };
              const addNotificationResult = await Admin.addNotification(notificationData);
            }
          }
        } else {
          res.send({ status: 0, message: 'Failed to add material inventory' })
        }
      }

      const selectedServices = req?.body?.values?.services;
      for (const row of selectedServices) {
        console.log('row productsssssss', row)
        const service_cost = Number(row?.total_cost) || 0;
        // const id = crypto.randomBytes(6).readUIntLE(0, 6);
        // let barcode = String(id).padStart(14, '0');
        const addServiceData = {};
        // addServiceData['barcode'] = row?.meterial ==1 ? 'SW' + barcode : 'C' + barcode;
        addServiceData['receive_order_id'] = orderResult;
        addServiceData['service_id'] = row.id;
        addServiceData['price'] = parseFloat(service_cost);
        addServiceData['status'] = 1;
        addServiceData['added_by'] = req.body.userId;
        addServiceData['timestamp'] = currentDate;

        const servResult = await Product.CreateReceiveOrderServices(addServiceData);

        if (servResult) {

          // Notification
          let notificationMsg = `Order "${orderData.name}" created for service "${row?.name}"`;
          let notificationUrl = "receive_inventory";

          const incrementResult_User = await Admin.incrementField(addServiceData.added_by, "noti_count", 1);
          let notificationData_user = {
            user_id: addServiceData.added_by,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: addServiceData.added_by,
          };
          const addNotificationResult_user = await Admin.addNotification(notificationData_user);

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};
          for (const adminId of adminIds) {
            if (adminId != addServiceData.added_by) {
              const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
              notificationData = {
                user_id: adminId,
                url: notificationUrl,
                timestamp: currentDate,
                message: notificationMsg,
                status: 1,
                from_id: addServiceData.added_by,
              };
              const addNotificationResult = await Admin.addNotification(notificationData);
            }
          }
        } else {
          res.send({ status: 0, message: 'Failed to add material inventory' })
        }
      }

      res.send({ status: 1, message: `Order "${orderData.name}" created successfully` })
    }
    else {
      res.send({ status: 0, message: 'Failed to create order' })
    }
  }
  catch (error) {
    console.log("Error adding finished product : ", error);
    res.send({ status: 500, message: 'Error adding finished product', error: error })
  }
}

const EditReceiveOrder = async (req, res) => {
  try {
    let currentDate = moment().unix();
    let receivedDate = req.body.values.receivedDate ? moment(req.body.values.receivedDate).unix() : null;
    let expectedDate = req.body.values.expectedDate ? moment(req.body.values.expectedDate).unix() : null;
    const { orderId, userId } = req.body
    const orderData = {};
    orderData['name'] = req.body.values.name;
    orderData['vendor_id'] = req.body.values.vendor;
    orderData['transaction_id'] = req.body.values.transactionId;
    orderData['received_date'] = receivedDate;
    orderData['expected_date'] = expectedDate;
    orderData['include_tax'] = req.body.values.includeTax;
    orderData['sub_total'] = req.body.values.sub_total || 0;
    orderData['tax'] = req.body.values.tax;
    orderData['grand_total'] = req.body.values.grand_total || 0;
    orderData['tax_category'] = req.body.values.taxCategory || 0;
    orderData['orderType'] = req.body.values.orderType == "stockCatalog" ? 2 : req.body.values.orderType == "serviceOnly" ? 3 : 1;
    orderData['project_type'] = req?.body?.values?.projectType?.id || null;
    orderData['ship_via'] = req.body.values.shipVia || null;
    orderData['notes'] = req.body.values.notes || null;
    orderData['sales_order_id'] = req?.body?.values?.salesOrderId || null;

    const orderResult = await Product.EditReceiveOrder(orderId, orderData)
    let result, materialData;
    if (orderResult) {
      const deletedOrderProducts = await Product.deleteOrderProducts(orderId)
      const products = req.body.values.products || [];
      if (products?.length > 0) {
        const catalogs = req.body.values.stockCatalogs || [];
        let cobinedProducts = [...products, ...catalogs];
        for (const row of cobinedProducts) {
          // let barcode = crypto.randomBytes(7).toString("hex");
          const id = crypto.randomBytes(6).readUIntLE(0, 6);
          let barcode = String(id).padStart(14, '0');
          const qty = row.qty ? row.qty : 0;
          const cost = row.unit_cost ? row.unit_cost : 0;
          const shipping_cost = row.shipping_cost ? row.shipping_cost : 0;
          const service_cost = Number(row.services?.total_cost) || 0;
          const service_id = row.services?.id || null;
          const addData = {};
          addData['barcode'] = row?.meterial == 1 ? 'SW' + barcode : 'C' + barcode;
          addData['material_id'] = row.id;
          addData['unit_cost'] = parseFloat(cost);
          addData['actual_qty'] = parseFloat(qty);
          addData['qty'] = parseFloat(qty);
          // addData['actual_total_cost'] = parseFloat(cost * qty);
          // addData['total_cost'] = parseFloat(cost * qty);
          addData['actual_total_cost'] = parseFloat((cost * qty) + (shipping_cost * qty) + service_cost);
          addData['total_cost'] = parseFloat((cost * qty) + (shipping_cost * qty) + service_cost);
          addData['status'] = 1;
          addData['added_by'] = userId;
          addData['source'] = 1;
          addData['timestamp'] = currentDate;
          addData['receive_order_id'] = orderId;
          addData['shipping_cost'] = parseFloat(shipping_cost);
          addData['service_cost'] = parseFloat(service_cost);
          addData['service_id'] = parseFloat(service_id) || null;
          result = await Product.addFinishedProduct(addData);
          materialData = await Product.getProductById(addData.material_id)

          if (result && materialData) {


            // Notification
            let notificationMsg = `Successfully updated ${row?.meterial == 1 ? "material" : "catalog"} "${materialData[0]?.name}" in Order ${orderData['name']}`;
            let notificationUrl = "receive_inventory";

            const incrementResult_User = await Admin.incrementField(userId, "noti_count", 1);
            let notificationData_user = {
              user_id: userId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: userId,
            };
            const addNotificationResult_user = await Admin.addNotification(notificationData_user);

            let adminIds = await Admin.getAllAdminIds();
            let notificationData = {};
            for (const adminId of adminIds) {
              if (adminId != userId) {
                const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
                notificationData = {
                  user_id: adminId,
                  url: notificationUrl,
                  timestamp: currentDate,
                  message: notificationMsg,
                  status: 1,
                  from_id: userId,
                };
                const addNotificationResult = await Admin.addNotification(notificationData);
              }
            }
          }
        }
      }

      // Handle services for service-only orders
      const deletedOrderServices = await Product.DeleteReceiveOrderServices(orderId)
      const selectedServices = req?.body?.values?.services || [];
      if (selectedServices?.length > 0) {
        for (const row of selectedServices) {
          const service_cost = Number(row?.total_cost) || 0;
          const addServiceData = {};
          addServiceData['receive_order_id'] = orderId;
          addServiceData['service_id'] = row.id;
          addServiceData['price'] = parseFloat(service_cost);
          addServiceData['status'] = 1;
          addServiceData['added_by'] = userId;
          addServiceData['timestamp'] = currentDate;

          const servResult = await Product.CreateReceiveOrderServices(addServiceData);

          if (servResult) {

            // Notification
            let notificationMsg = `Order "${orderData.name}" updated for service "${row?.name || row?.description}"`;
            let notificationUrl = "receive_inventory";

            const incrementResult_User = await Admin.incrementField(addServiceData.added_by, "noti_count", 1);
            let notificationData_user = {
              user_id: addServiceData.added_by,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: addServiceData.added_by,
            };
            const addNotificationResult_user = await Admin.addNotification(notificationData_user);

            let adminIds = await Admin.getAllAdminIds();
            let notificationData = {};
            for (const adminId of adminIds) {
              if (adminId != addServiceData.added_by) {
                const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
                notificationData = {
                  user_id: adminId,
                  url: notificationUrl,
                  timestamp: currentDate,
                  message: notificationMsg,
                  status: 1,
                  from_id: addServiceData.added_by,
                };
                const addNotificationResult = await Admin.addNotification(notificationData);
              }
            }
          } else {
            res.send({ status: 0, message: 'Failed to add service to order' })
            return;
          }
        }
      }

      res.send({ status: 1, message: `Order ${orderData['name']} updated successfully` })
    }
    else {
      res.send({ status: 0, message: 'Failed to update order' })
    }
  }
  catch (error) {
    console.log("Error updating finished product : ", error);
    res.send({ status: 500, message: 'Error updating finished product', error: error })
  }
}

const cancelReceiveOrder = async (req, res) => {
  try {
    let currentDate = moment().unix();
    const { orderId, userId } = req.body
    console.log('orderId...', orderId)
    const orderResult = await Product.getOrderById(orderId,true)
    const cancelResult = await Product.cancelReceiveOrder(orderId)
    const deletedOrderProducts = await Product.deleteOrderProducts(orderId)
    if (cancelResult && deletedOrderProducts && orderResult  && orderResult.length > 0) {
      // Notification
      let notificationMsg = `Order ${orderResult[0].order_name} has been cancelled`;
      let notificationUrl = "receive_inventory";

      const incrementResult_User = await Admin.incrementField(userId, "noti_count", 1);
      let notificationData_user = {
        user_id: userId,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: userId,
      };
      const addNotificationResult_user = await Admin.addNotification(notificationData_user);

      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != userId) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
      res.send({ status: 1, message: notificationMsg })
    }
    else {
      res.send({ status: 0, message: 'Failed to cancel order' })
    }
  }
  catch (error) {
    console.log("Error cancelling order : ", error);
    res.send({ status: 500, message: 'Error cancelling order', error: error })
  }
}

///////////////////////qb
const fetchAllPaidBills = async () => {
  const token = await getNewAccessToken();

  const apiUrl = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query`;

  const query = `
    SELECT Id, DocNumber, TxnDate, DueDate, TotalAmt, Balance, VendorRef, MetaData
    FROM Bill
    WHERE Balance = '0'
    ORDER BY TxnDate DESC
  `;

  const encodedQuery = encodeURIComponent(query);

  try {
    const response = await axios.get(`${apiUrl}?query=${encodedQuery}&minorversion=65`, {
      headers: {
        Authorization: `Bearer ${token}`,
        Accept: 'application/json',
        'Content-Type': 'application/json'
      },
      timeout: 15000
    });

    const bills = response.data.QueryResponse.Bill || [];
    // console.log(`✅ Retrieved ${bills.length} paid bills.`,bills);
    return bills;

  } catch (error) {
    console.error("❌ Error fetching paid bills:", error.response?.data || error.message);
    return [];
  }
};

const createJournalEntryForPurchaseOrderBilled = async (grandTotal, docNum = '', txnDate = moment().format('YYYY-MM-DD'), vendorId, debitAccId, creditAccId, description) => {
  try {
    const token = await getNewAccessToken();

    const journalEntry = {
      Line: [
        {
          Id: "1",
          Description: description,                 //`Material purchase recorded for PO# ${docNum}`,
          Amount: grandTotal,
          DetailType: "JournalEntryLineDetail",
          JournalEntryLineDetail: {
            PostingType: "Debit",
            AccountRef: {
              value: debitAccId     //"182"     //inventoryAsset       //      "179" // 🧾 Purchases (Expense) - hits the P&L
            },
            DepartmentRef: { value: departmentId },
            Entity: {
              Type: "Vendor",
              EntityRef: {
                value: vendorId.toString()
              }
            }
          }
        },
        {
          Id: "2",
          Description: description,             //           `Accounts Payable entry for PO# ${docNum}`,
          Amount: grandTotal,
          DetailType: "JournalEntryLineDetail",
          JournalEntryLineDetail: {
            PostingType: "Credit",
            AccountRef: {
              value: creditAccId      //"189"        //accountsPayable            // "182"  idk
            },
            DepartmentRef: { value: departmentId },
            Entity: {
              Type: "Vendor",
              EntityRef: {
                value: vendorId.toString()
              }
            }
          }
        }
      ],
      TxnDate: txnDate,
      PrivateNote: `Auto posted JE from PO#${docNum}`
    };

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

    const response = await axios.post(`${apiUrl}/journalentry`, journalEntry, {
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
        Accept: 'application/json'
      }
    });

    if (response.status === 200 || response.status === 201) {
      console.log("✅ Journal Entry created successfully 1:", response.data);
      return response.data;
    } else {
      console.error("❌ Failed to create Journal Entry:", response.data);
      return null;
    }

  } catch (error) {
    console.error("Error creating Journal Entry 1:", error.response ? error.response.data : error.message);
    throw error;
  }
};

const makeAsPaidPoFromQb = async (qbPaidBills) => {
  if (!Array.isArray(qbPaidBills) || qbPaidBills.length === 0) {
    console.log("⚠️ No paid bills to process.");
    return;
  }

  for (const bill of qbPaidBills) {
    const billId = bill?.Id;

    if (!billId) {
      console.warn("⚠️ Skipping bill with missing ID:", bill);
      continue;
    }

    try {
      // Fetch your system's product/order data linked to this QBO Bill ID
      const prodData = await Product.getOrderProductsFromQbBillId(billId);

      if (!prodData || prodData.length === 0) {
        console.log(`🔍 No matching internal products found for QBO Bill ID: ${billId}`);
        continue;
      }




      for (const item of prodData) {
        console.log('prodDataprodData', item)
        const isNonInvMaterial = item?.material_types === 2 || false;

        let accountsPayableAccId = PRODUCTION ? "2011000" : "2000"
        // const debitAccId = isNonInvMaterial ?await findAccountIdByAcctNum(5025000)  :await findAccountIdByAcctNum(1013000)   // "1150040015" : "182";  // 181 COGS  184 = COGS - Job Materials, 182 = Inventory
        // const creditAccId = isNonInvMaterial ? await findAccountIdByAcctNum (accountsPayableAccId) :await findAccountIdByAcctNum(accountsPayableAccId)   //"189" : "189"; // 179 = Purchases, 189 = A/P

        const description = `Purchased material has billed "${item?.name}" under Purchase Order "${item?.order_name}" with a quantity of ${item?.qty} at a unit cost of $${item?.unit_cost.toFixed(2)}, resulting in a total cost of $${item?.total_cost.toFixed(2)}.`;

        // createJournalEntryForPurchaseOrderBilled(
        //     item.total_cost,                         // GrandTotal
        //     docNum,        // docNum
        //     bill.TxnDate,                      // TxnDate
        //     bill.VendorRef.value,                          // VendorRef.value
        //     debitAccId,creditAccId,
        //     description
        //   );

      }

      await Product.updateOrderStatusFromQbBillId(billId, 2, 1);

      console.log(`✅ Updated payment status for products linked to QBO Bill ID: ${billId}`);
    } catch (err) {
      console.error(`❌ Error processing QBO Bill ID: ${billId}`, err.message || err);
    }
  }
};

/////////////////////

const getAllReceiveOrders = async (req, res) => {
  try {
    const { userType, userId, where } = req.body
    // const qbPaidBills = await fetchAllPaidBills();
    //  await makeAsPaidPoFromQb(qbPaidBills)
    const orderData = await Product.getAllReceiveOrders(userType, userId, where);
    if (orderData) {
      res.send({ status: 1, message: 'Orders fetched Successfully', orderData: orderData })

    }
    else {
      res.send({ status: 0, message: 'Error fetching orders' })
    }
  }
  catch (error) {
    console.log("getAllReceiveOrders error - ", error);
    res.send({ status: 500, message: 'Error', error: error })
  }
}

const getAllReceiveOrdersLatestFirst = async (req, res) => {
  try {
    const { userType, userId, where } = req.body
    // const qbPaidBills = await fetchAllPaidBills();
    //  await makeAsPaidPoFromQb(qbPaidBills)
    const orderData = await Product.getAllReceiveOrdersLatestFirst(userType, userId, where);
    if (orderData) {
      res.send({ status: 1, message: 'Orders fetched Successfully', orderData: orderData })

    }
    else {
      res.send({ status: 0, message: 'Error fetching orders' })
    }
  }
  catch (error) {
    console.log("getAllReceiveOrdersLatestFirst error - ", error);
    res.send({ status: 500, message: 'Error', error: error })
  }
}

const getAllSalesOrdersForAutocomplete = async (req, res) => {
  try {
    const { userId, userType, search } = req.body;

    const page = 1;
    const rowPerpage = null;
    const where = 'r.quotation = 0 AND EXISTS (SELECT 1 FROM projects p WHERE p.request_id = r.id AND p.is_estimate_job = 0)';
    const sort = 'new';
    const fromDate = null;
    const toDate = null;
    const filterCommunity = null;
    const dueData = null;
    const filterClient = null;
    const filterProjectType = null;
    const filterUser = null;
    const splitOrder = null;
    const filterProjectCategory = null;

    const data = await Request.getAllRequestsOptimized(
      null,
      userId,
      userType,
      page,
      rowPerpage,
      search,
      where,
      sort,
      fromDate,
      toDate,
      filterCommunity,
      dueData,
      filterClient,
      filterProjectType,
      filterUser,
      splitOrder,
      filterProjectCategory
    );

    const list = Array.isArray(data?.data) ? data.data : [];

    const salesOrders = list.map((item) => ({
      id: item.id,
      name: item.name,
      doc_number: item.doc_number,
    }));

    res.send({
      status: 1,
      message: 'Sales orders fetched successfully',
      salesOrders,
    });
  } catch (error) {
    console.log("getAllSalesOrdersForAutocomplete error - ", error);
    res.send({ status: 500, message: 'Error fetching sales orders', error: error });
  }
};

const getOrderById = async (req, res) => {
  try {
    const { orderId, noStatusCheck } = req.body;
    const orderData = await Product.getOrderById(orderId, noStatusCheck);
    const prodData = await Product.getOrderProducts(orderId, noStatusCheck)
    const orderServices = await Product.getOrderServices(orderId)
    let allServices = await Product.getAllServices();

    // Check if orderData exists and has at least one row
    const hasOrderData = orderData && Array.isArray(orderData) && orderData.length > 0;

    if (hasOrderData) {
      // Send response with orderData - prodData and orderServices can be empty arrays for service-only or material-only orders
      res.send({ status: 1, message: 'Order details fetched Successfully', orderData: orderData, allServices, prodData: prodData || [], orderServices: orderServices || [] })
    }
    else {
      res.send({ status: 0, message: 'Error fetching order details' })
    }
  }
  catch (error) {
    console.log("getOrderById error - ", error);
    res.send({ status: 500, message: 'Error', error: error })
  }
}

//////////////////////////////////////qb
const findVendorIdByName = async (name) => {
  try {
    // Ensure valid access token
    let token = await getNewAccessToken();

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query`;

    const response = await axios.get(
      apiUrl,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/text'
        },
        params: {
          query: `SELECT * FROM Vendor WHERE DisplayName = '${name}'`
        }
      }
    );

    const vendors = response.data.QueryResponse.Vendor;
    console.log(vendors, "Vendors retrieved");

    if (vendors && vendors.length > 0) {
      return vendors[0].Id;
    } else {
      throw new Error(`Vendor with name ${name} not found`);
    }
  } catch (err) {
    console.error('Error finding vendor by name:', err.response ? err.response.data : err.message);
    throw err;
  }
};

const findItemByName = async (itemName) => {
  try {
    let token = await getNewAccessToken();

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query`;

    const response = await axios.get(apiUrl, {
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/text',
      },
      params: {
        query: `SELECT * FROM Item WHERE Name = '${itemName + locationInventoryCode}'`,
      },
    });

    const items = response.data.QueryResponse.Item;

    if (items && items.length > 0) {
      return items[0]; // Return the first matching item
    } else {
      throw new Error(`Item with name ${itemName} not found in QuickBooks.`);
    }
  } catch (error) {
    console.error('Error finding item by name:', error.response ? error.response.data : error.message);
    throw error;
  }
};

const findItemByNameForPo = async (itemName) => {
  try {
    let token = await getNewAccessToken();

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query`;

    const response = await axios.get(apiUrl, {
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/text',
      },
      params: {
        query: `SELECT * FROM Item WHERE Name = '${itemName}'`,
      },
    });

    const items = response.data.QueryResponse.Item;

    if (items && items.length > 0) {
      return items[0]; // Return the first matching item
    } else {
      throw new Error(`Item with name ${itemName} not found in QuickBooks.`);
    }
  } catch (error) {
    console.error('Error finding item by name:', error.response ? error.response.data : error.message);
    throw error;
  }
};

////////////////////////////////////qb
let docNum = null
let bill_id = null

const createPurchaseOrderFromData = async (orderPreviewData, vendorData, taxAmount, GrandTotal, orderId = null, orderType = 1) => {
  try {
    // Ensure that vendor information is available

    const additionalData = {
      ...orderPreviewData[0], // Copy all properties from the first item
      product_name: 'tax',
      unit_cost: taxAmount || 0,
      qty: 1,
      total_cost: taxAmount || 0
    };

    // const combinedData = [...orderPreviewData, additionalData];
    const combinedData = [];

    // For service-only orders, fetch services from receive_order_services table
    if (orderType === 3 && orderId) {
      const orderServices = await Product.getOrderServices(orderId);
      for (const service of orderServices) {
        combinedData.push({
          ...orderPreviewData[0], // Use order metadata from first item
          product_name: service.name || service.description || 'Service',
          unit_cost: parseFloat(service.price) || 0,
          qty: 1,
          total_cost: parseFloat(service.price) || 0,
          isService: true,
          qbId: service.service_qb_id || null // Use service QuickBooks ID
        });
      }
    } else {
      // For materials/catalogs orders, process orderPreviewData
      for (const item of orderPreviewData) {
        // Original item
        combinedData.push({ ...item, qbId: item?.material_qb_id, total_cost: parseFloat((item?.unit_cost || 0) * (item?.qty || 1)).toFixed(2) });

        // Add service line if applicable
        if (item.service_id) {
          combinedData.push({
            ...item,
            product_name: item.service_name || 'Service',
            unit_cost: item.service_cost || 0,
            qty: 1,
            total_cost: item?.service_cost || 0,
            isService: true,
            qbId: item?.service_qb_id
          });
        }

        // Add shipping line if applicable
        // if (item.shipping_cost && item.shipping_cost > 0) {
        //   const itemId = await findItemByNameForPo("Shipping Cost"); 
        //   combinedData.push({
        //     ...item,
        //     product_name: 'Shipping Cost',
        //     unit_cost: item.shipping_cost,
        //     qty: item?.qty || 0,
        //     total_cost:parseFloat((item?.shipping_cost || 0) * item?.qty || 1).toFixed(2),
        //     isShipping: true,
        //     qbId :itemId.Id

        //   });
        // }
      }
    }

    // Add tax line
    if (taxAmount && taxAmount > 0) {
      const itemId = await findItemByNameForPo('Tax');
      combinedData.push({
        ...orderPreviewData[0],
        product_name: 'Tax',
        unit_cost: taxAmount,
        qty: 1,
        total_cost: taxAmount,
        isTax: true,
        qbId: itemId.Id
      });
    }

    // Now you can use `combinedData` for your purchase order
    console.log("Prepared Combined Data for PO:", combinedData);


    const quickbookCustomerName = (vendorData.quickbookCustomerName + locationInventoryCode).trim();
    const vendorId = vendorData?.partnerQbId ? vendorData?.partnerQbId : await findVendorIdByName(quickbookCustomerName);
    console.log('Vendor ID:', vendorId);

    if (!vendorId) {
      console.error(`Vendor with name ${quickbookCustomerName} not found in QuickBooks.`);
      throw new Error("Vendor is required to create a purchase order.");
    }

    // Prepare items for QuickBooks
    const items = await Promise.all(combinedData.map(async (item) => {
      let qbId = item.qbId || null;
      const itemId = qbId || await findItemByName(item.product_name);
      console.log('itemIditemIditemId', itemId)
      if (!itemId) {
        console.warn(`Item not found for product name: ${item.product_name}`);
        return null; // Skip items not found
      }
      return {
        Amount: Number(item.total_cost), // Use the total cost from orderPreviewData
        DetailType: "ItemBasedExpenseLineDetail",
        ItemBasedExpenseLineDetail: {
          ItemRef: { value: qbId || itemId.Id }, // Use the found item ID
          Qty: Number(item.qty), // Use the quantity from orderPreviewData
          UnitPrice: Number(item.unit_cost) // Use the unit cost from orderPreviewData
        },
        ClassRef: { value: item.project_type_quickbook_id ? item.project_type_quickbook_id : null }
      };
    }));

    // Filter out any null items
    const validItems = items.filter(item => item !== null);

    // Prepare the purchase order object
    const purchaseOrder = {
      VendorRef: { value: vendorId }, // Correctly set the vendor ID
      Line: validItems, // Valid line items
      PrivateNote: "Purchase order created from received data.",
      TxnDate: moment().format('YYYY-MM-DD') // Set the transaction date to today
    };

    // Call QuickBooks API to create the purchase order

    const response = await addPurchaseOrderToQuickBooks(purchaseOrder, GrandTotal, orderPreviewData);

    console.log('QuickBooks Purchase Order Response:', response);

    return response; // Return the response or handle as needed
  } catch (error) {
    console.error("Error in creating purchase order:", error);
    throw error; // Rethrow error for further handling if necessary
  }
};

// Adding the QuickBooks API integration function remains unchanged
const addPurchaseOrderToQuickBooks = async (purchaseOrderData, GrandTotal, orderPreviewData) => {
  console.log("Attempting to add purchase order to QuickBooks...", purchaseOrderData);
  let token = await getNewAccessToken();

  const postPurchaseOrder = async (useNewToken = false, attempt = 0) => {
    if (useNewToken) {
      console.log('Refreshing access token...');
      token = await getNewAccessToken();
    }

    try {
      const quickBooksPurchaseOrderData = {
        VendorRef: { value: String(purchaseOrderData.VendorRef.value) }, // Use vendor ID
        TxnDate: purchaseOrderData.TxnDate, // Transaction date
        DepartmentRef: { value: String(departmentId) },
        Line: purchaseOrderData.Line.map(item => {
          const lineDetail = {
            ItemRef: { value: String(item.ItemBasedExpenseLineDetail.ItemRef.value) }, // Ensure string type
            Qty: item.ItemBasedExpenseLineDetail.Qty,
            UnitPrice: item.ItemBasedExpenseLineDetail.UnitPrice
          };

          // Only include ClassRef if it has a valid value (QuickBooks rejects null values)
          // Note: ClassRef should be at the line level, not inside ItemBasedExpenseLineDetail
          if (item.ClassRef && item.ClassRef.value !== null && item.ClassRef.value !== undefined) {
            lineDetail.ClassRef = { value: String(item.ClassRef.value) };
          }

          const lineItem = {
            Amount: item.Amount,
            DetailType: "ItemBasedExpenseLineDetail",
            ItemBasedExpenseLineDetail: lineDetail
          };

          // ClassRef can also be at the line level (outside ItemBasedExpenseLineDetail)
          // QuickBooks accepts it either way, but let's keep it inside ItemBasedExpenseLineDetail for consistency

          return lineItem;
        }),
        PrivateNote: purchaseOrderData.PrivateNote, // Optional note
      };

      console.log("QuickBooks Purchase Order Data:", JSON.stringify(quickBooksPurchaseOrderData, null, 2));

      const apiUrl = PRODUCTION
        ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
        : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

      const response = await axios.post(
        `${apiUrl}/purchaseorder`,
        quickBooksPurchaseOrderData,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
            Accept: 'application/json'
          },
          timeout: 30000 // 30 seconds
        }
      );

      if (response.status === 200 || response.status === 201) {
        console.log("Purchase order has been successfully added to QuickBooks:", response.data);
        docNum = response.data.PurchaseOrder.DocNumber;
        quickbookId = response.data.PurchaseOrder.Id;

        const billData = await createBillForPurchaseOrder(response.data.PurchaseOrder, purchaseOrderData.TxnDate);

        if (purchaseOrderData.VendorRef.value) {



          if (Array.isArray(orderPreviewData)) {
            for (const item of orderPreviewData) {
              if (item?.vendor_id && item?.total_cost) {
                const isNonInvMaterial = item?.material_types === 2 || false;
                console.log('isNonInvMaterial', isNonInvMaterial)
                let accountsPayableAccId = PRODUCTION ? "2011000" : "2000"
                // const debitAccId = isNonInvMaterial ? await findAccountIdByAcctNum(5025000)  :await findAccountIdByAcctNum(1013000)    //"1150040015" : "182";  // 181 COGS  184 = COGS - Job Materials, 182 = Inventory
                // const creditAccId = isNonInvMaterial ? await findAccountIdByAcctNum (accountsPayableAccId) :await findAccountIdByAcctNum(accountsPayableAccId)   //"189" : "189"; // 179 = Purchases, 189 = A/P

                //  const description = `Purchased material "${item?.product_name}" with a quantity of ${item?.qty} at a unit cost of $${item?.unit_cost.toFixed(2)}, resulting in a total cost of $${item?.total_cost.toFixed(2)}.`;
                const description = `Purchased material "${item?.product_name}" under Purchase Order "${item?.order_name}" with a quantity of ${item?.qty} at a unit cost of $${item?.unit_cost.toFixed(2)}, resulting in a total cost of $${item?.total_cost.toFixed(2)}.`;

                // createJournalEntryForPurchase(
                //     item.total_cost,                         // GrandTotal
                //     docNum,        // docNum
                //     purchaseOrderData.TxnDate,                      // TxnDate
                //     purchaseOrderData.VendorRef.value,                          // VendorRef.value
                //     debitAccId,creditAccId,
                //     description
                //   );
              }
            }
          }

        }
        return response.data; // Return the response
      } else {
        console.error("Failed to add the purchase order to QuickBooks:", response.data);
        return null; // Return null on failure
      }

    } catch (error) {
      console.error("Error adding purchase order to QuickBooks:", error.response ? error.response.data : error.message);
      if (error.response && error.response.data && error.response.data.Fault && error.response.data.Fault.Error) {
        console.error("Detailed Error Information:", JSON.stringify(error.response.data.Fault.Error, null, 2));
      }
      throw error; // Rethrow error for further handling if necessary
    }
  };

  return postPurchaseOrder(); // Start the posting process
};

const createJournalEntryForPurchase = async (grandTotal, docNum = '', txnDate = moment().format('YYYY-MM-DD'), vendorId, debitAccId, creditAccId, description) => {
  try {
    console.log("grandTotalgrandTotal", grandTotal)
    const token = await getNewAccessToken();

    const journalEntry = {
      Line: [
        {
          Id: "1",
          Description: description,                 //`Material purchase recorded for PO# ${docNum}`,
          Amount: grandTotal,
          DetailType: "JournalEntryLineDetail",
          JournalEntryLineDetail: {
            PostingType: "Debit",
            AccountRef: {
              value: debitAccId     //"182"     //inventoryAsset       //      "179" // 🧾 Purchases (Expense) - hits the P&L
            },
            DepartmentRef: { value: departmentId },
            Entity: {
              Type: "Vendor",
              EntityRef: {
                value: vendorId.toString()
              }
            }
          }
        },
        {
          Id: "2",
          Description: description,             //           `Accounts Payable entry for PO# ${docNum}`,
          Amount: grandTotal,
          DetailType: "JournalEntryLineDetail",
          JournalEntryLineDetail: {
            PostingType: "Credit",
            AccountRef: {
              value: creditAccId      //"189"        //accountsPayable            // "182"  idk
            },
            DepartmentRef: { value: departmentId },
            Entity: {
              Type: "Vendor",
              EntityRef: {
                value: vendorId.toString()
              }
            }
          }
        }
      ],
      TxnDate: txnDate,
      // CurrencyRef: { value: 'USD' },
      PrivateNote: `Auto posted JE from PO#${docNum}`
    };

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

    const response = await axios.post(`${apiUrl}/journalentry`, journalEntry, {
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
        Accept: 'application/json'
      }
    });

    if (response.status === 200 || response.status === 201) {
      console.log("✅ Journal Entry created successfully 2:", response.data);
      return response.data;
    } else {
      console.error("❌ Failed to create Journal Entry:", response.data);
      return null;
    }

  } catch (error) {
    console.error("Error creating Journal Entry 2:", error.response ? error.response.data : error.message);
    return null
  }
};


const createBillForPurchaseOrder = async (poData, txnDate) => {
  const token = await getNewAccessToken();
  bill_id = null
  const apiUrl = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

  const billPayload = {
    VendorRef: {
      value: poData.VendorRef.value
    },
    TxnDate: txnDate,
    // DocNumber: poData.DocNumber,   // 👈 THIS SETS THE P.O. NUMBER FIELD
    // PONumber: poData.DocNumber,
    DepartmentRef: { value: departmentId },
    Line: poData.Line.map(item => ({
      Amount: item.Amount,
      DetailType: "ItemBasedExpenseLineDetail",
      ItemBasedExpenseLineDetail: {
        ItemRef: {
          value: item.ItemBasedExpenseLineDetail.ItemRef.value
        },
        Qty: Number(item.ItemBasedExpenseLineDetail.Qty),
        UnitPrice: Number(item.ItemBasedExpenseLineDetail.UnitPrice),
        ClassRef: {
          value: item?.ItemBasedExpenseLineDetail?.ClassRef ? item?.ItemBasedExpenseLineDetail?.ClassRef?.value : null
        }
      }
    })),
    PrivateNote: `Bill created from PO #${poData.DocNumber}`,
    LinkedTxn: [
      {
        TxnId: poData.Id,
        TxnType: "PurchaseOrder"
      }
    ],
    // CustomField: [
    //   {
    //     DefinitionId: "2", // Replace this with your actual custom field DefinitionId
    //     Name: "P.O. Number", // The name of your custom field
    //     Type: "StringType",
    //     StringValue: (poData.DocNumber || "")?.slice(0, 30) // The value you want to set in the custom field
    //   },
    //   {
    //     DefinitionId: "3", // Replace this with your actual custom field DefinitionId
    //     Name: "P.O. Number", // The name of your custom field
    //     Type: "StringType",
    //     StringValue: (poData.DocNumber || "")?.slice(0, 30) // The value you want to set in the custom field
    //   },

    // ]
  };

  const response = await axios.post(`${apiUrl}/bill`, billPayload, {
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      Accept: 'application/json'
    },
    timeout: 15000
  });

  console.log("✅ Bill created from Purchase Order:", response.data);
  bill_id = response.data.Bill.Id;
  return response.data;
};

const getPurchaseOrderById = async (purchaseOrderId) => {
  console.log("Attempting to get purchase order from QuickBooks...", purchaseOrderId);

  let token = await getNewAccessToken();

  const getPurchaseOrder = async (useNewToken = false, attempt = 0) => {
    if (useNewToken) {
      console.log('Refreshing access token...');
      token = await getNewAccessToken();
    }

    try {
      const apiUrl = PRODUCTION
        ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
        : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

      const response = await axios.get(
        `${apiUrl}/purchaseorder/${purchaseOrderId}`,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
            Accept: 'application/json'
          },
          timeout: 30000 // 30 seconds
        }
      );

      if (response.status === 200) {
        console.log("Successfully retrieved purchase order from QuickBooks:", response.data);
        console.log("customFields", response.data.PurchaseOrder.CustomField);
        return response.data; // Return the retrieved purchase order
      } else {
        console.error("Failed to retrieve the purchase order from QuickBooks:", response.data);
        return null; // Return null on failure
      }
    } catch (error) {
      console.error("Error retrieving purchase order from QuickBooks:", error.response ? error.response.data : error.message);
      if (error.response && error.response.data && error.response.data.Fault && error.response.data.Fault.Error) {
        console.error("Detailed Error Information:", JSON.stringify(error.response.data.Fault.Error, null, 2));
      }
      throw error; // Rethrow error for further handling if necessary
    }
  };

  return getPurchaseOrder(); // Start the retrieval process
};

// (async () => {
//   const response = await getPurchaseOrderById(11888);
// })();

const getBillById = async (billId) => {
  const token = await getNewAccessToken();

  const apiUrl = PRODUCTION
    ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
    : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

  try {
    // Fetching the bill and trying to include custom fields
    const response = await axios.get(`${apiUrl}/bill/${billId}`, {
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
        Accept: 'application/json'
      },
      timeout: 15000
    });

    if (response.status === 200) {
      console.log("✅ Bill fetched successfully:", response.data);

      // // Check if custom fields exist in the response
      // if (response.data.Bill.Line) {
      //   console.log("Custom Fields:", response.data.Bill.Line);
      // } else {
      //   console.log("No custom fields found in this bill.");
      // }

      return response.data; // Return the bill data
    } else {
      console.error("Failed to fetch the bill:", response.data);
      return null; // Return null if the fetch fails
    }
  } catch (error) {
    console.error("Error fetching bill:", error.response ? error.response.data : error.message);
    if (error.response && error.response.data && error.response.data.Fault && error.response.data.Fault.Error) {
      console.error("Detailed Error Information:", JSON.stringify(error.response.data.Fault.Error, null, 2));
    }
    throw error; // Rethrow error for further handling if necessary
  }
};

// (async () => {
//   const response = await getBillById(12322);
// })();

////////////////////////////////////////

const updateReceiveOrder = async (req, res) => {
  try {
    const { orderId, data, userId, orderPreviewData } = req.body;
    let notificationMsg = "";
    let currentDate = moment().unix();
    quickbookId = null
    docNum = null
    if (orderPreviewData && orderPreviewData?.length > 0) {
      // let taxAmount =orderPreviewData[0].include_tax==1?
      //  parseFloat(Number(orderPreviewData[0].grand_total) - Number(orderPreviewData[0].sub_total)).toFixed(2) : 0
      let taxAmount = orderPreviewData[0]?.tax || 0
      let GrandTotal = orderPreviewData[0]?.grand_total || 0
      const orderType = orderPreviewData[0]?.orderType || 1; // 1 = Materials, 2 = Stock Catalogs, 3 = Service Only
      console.log("orderPreviewDataorderPreviewData",orderPreviewData)
      let notSyncedVendors =[]
      const vendorData = await Product.getVendorData(orderPreviewData[0].vendor_id)
       notSyncedVendors = 
        Array.from(
          new Set(
            orderPreviewData
              .filter(item => item.vendor_synced != 1) // Filter for vendors not synced
              .map(item => item.vendor_name) // Extract vendor names
          )
        );
        if(vendorData.partnerQbId && vendorData.syncWithQuickBook == 1){
        const vendorResultFromQb = await validateVendorInQb(vendorData.partnerQbId);
        console.log("vendorResultFromQb",vendorResultFromQb)
        if(vendorResultFromQb.removeQbid){
          notSyncedVendors.push(vendorData.name)
          const updateData ={
            syncWithQuickBook: 0,
            partnerQbId: null
          }
          const updateVendor = await Client.updateVendorByQbId(vendorData.partnerQbId,updateData);
        }
        }
      
      let notSyncedMaterials = [];
      let notSyncedCatalogs = [];
      let notSyncedServices = [];

      // For service-only orders, only check services, not materials
      if (orderType === 3) {
        // Service-only order: fetch services from receive_order_services table
        const orderServices = await Product.getOrderServices(orderId);
        notSyncedServices = orderServices
          .filter(item => item.syncWithQuickbooks != 1) // Filter for services not synced
          .map(item => item.name || item.description || ''); // Extract service names

          const syncedServices = orderServices?.filter(item => item.syncWithQuickbooks == 1 && item.service_qb_id) 
          console.log("syncedServices",syncedServices)
          for(const service of syncedServices){
          const serviceResultFromQb = await validateInventoryItemsInQbForPo(service,"service_qb_id");
          console.log("serviceResultFromQb",serviceResultFromQb)
          if(serviceResultFromQb?.itemData){
            notSyncedServices.push(serviceResultFromQb?.itemData?.name)
          const updateData ={
              syncWithQuickbooks: 0,
              quickbookId: null
            }
            const updateService = await Product.updateServiceByQbId(service.service_qb_id,updateData);
          }
          }
      } else {
        // Materials/Catalogs orders: check materials and services from orderPreviewData
        notSyncedMaterials = orderPreviewData
          .filter(item => item.material_synced != 1 && item.product_name && item.meterial) // Filter for materials not synced and has product_name
          .map(item => item.product_name); // Extract material names
        
          notSyncedCatalogs =orderPreviewData
          .filter(item => item.material_synced != 1 && item.product_name && !item.meterial) // Filter for materials not synced and has product_name
          .map(item => item.product_name); // Extract material names

        notSyncedServices = orderPreviewData
          .filter(item => (item.serviceSyncedWithQb != 1 && item.service_id != null)) // Filter for services not synced
          .map(item => item.service_name); // Extract service names

          ////////////////////////validate qb items
          const syncedCatalogs = orderPreviewData?.filter(item => item.material_synced == 1 && item.product_name && !item.meterial) 
          console.log("syncedCatalogs",syncedCatalogs)
          for(const catalog of syncedCatalogs){
          const catalogResultFromQb = await validateInventoryItemsInQbForPo(catalog,"material_qb_id");
          console.log("catalogResultFromQb",catalogResultFromQb)
          if(catalogResultFromQb?.itemData){
          notSyncedCatalogs.push(catalogResultFromQb?.itemData?.product_name)
          const updateData ={
            syncWithQuickbooks: 0,
            quickbookId: null
          }
          const updateProduct = await Product.updateProductByQbId(catalog.material_qb_id,updateData);
          }
          }

          const syncedMaterials = orderPreviewData?.filter(item => item.material_synced == 1 && item.product_name && item.meterial) 
          console.log("syncedMaterials",syncedMaterials)
          for(const material of syncedMaterials){
          const materialResultFromQb = await validateInventoryItemsInQbForPo(material,"material_qb_id");
          console.log("materialResultFromQb",materialResultFromQb)
          if(materialResultFromQb?.itemData){
          notSyncedMaterials.push(materialResultFromQb?.itemData?.product_name)
          const updateData ={
            syncWithQuickbooks: 0,
            quickbookId: null
          }
          const updateMaterial = await Product.updateProductByQbId(material.material_qb_id,updateData);
          }
          }

          const syncedServices = orderPreviewData?.filter(item => item.serviceSyncedWithQb == 1 && item.service_qb_id) 
          console.log("syncedServices",syncedServices)
          for(const service of syncedServices){
          const serviceResultFromQb = await validateInventoryItemsInQbForPo(service,"service_qb_id");
          console.log("serviceResultFromQb",serviceResultFromQb)
          if(serviceResultFromQb?.itemData){
          notSyncedServices.push(serviceResultFromQb?.itemData?.service_name)
          const updateData ={
            syncWithQuickbooks: 0,
            quickbookId: null
          }
          const updateService = await Product.updateServiceByQbId(service.service_qb_id,updateData);
          }
          }
      }
      // Combine results into a message
      const notSyncedMessage = 
        (notSyncedVendors.length > 0 || notSyncedMaterials.length > 0 || notSyncedServices.length > 0 || notSyncedCatalogs.length > 0)
        ? 
          'This order is not synced with QuickBooks because the following are not in QuickBooks: <br/>' +
          (notSyncedVendors.length > 0 ? `Vendors : ${notSyncedVendors.join(', ')} . <br/>` : '') +
          (notSyncedMaterials.length > 0 ? `Materials : ${notSyncedMaterials.join(', ')} . <br/>` : '') + 
          (notSyncedCatalogs.length > 0 ? `Catalogs : ${notSyncedCatalogs.join(', ')} . <br/>` : '') + 
          (notSyncedServices.length > 0 ? `Services : ${notSyncedServices.join(', ')} . <br/>` : '') 
        : 'Successfully Synced With QuickBooks';

      console.log("notSyncedMessage", notSyncedMessage)
      data.notSyncedMessage = notSyncedMessage
      
      if(notSyncedVendors.length==0 && notSyncedMaterials.length==0 && notSyncedServices.length==0 && notSyncedCatalogs.length==0){
        console.log('inside if')
        try {
         
          // if(orderType === 3){
          //   console.log('service only order. No QB Purchase Order is created.')
          // }else{
          let qbResponse = await createPurchaseOrderFromData(orderPreviewData, vendorData, taxAmount, GrandTotal, orderId, orderType)
          // }
        } catch (error) {
          console.log("error", error)
        }
      }
    }

    if (docNum && quickbookId && bill_id) {
      data.syncWithQuickbooks = 1
      data.quickbookId = quickbookId
      data.doc_number = docNum
      data.qb_bill_id = bill_id
    }

    const updated = await Product.updateReceiveOrder(orderId, data);
    const prodData = await Product.getOrderProducts(orderId)

    for (const row of prodData) {
      const qty = row.quantity ? row.quantity : row.qty ? row.qty : 0;
      const addData = {};
      addData['material_id'] = row.id;
      addData['actual_qty'] = parseFloat(qty);

      let materialData = await Product.getProductById(addData.material_id)
      const updateData = {
        price: materialData[0].max_price || 0
      }
      const updateMaterialPrice = await Product.updateProduct(addData.material_id, updateData)
    
      if(quickbookId){
      // Update the quantity in QuickBooks
      const updatedProduct = await updateQuickBooksProductQuantityByQuickbookId(materialData[0].quickbookId, addData.actual_qty, materialData[0].total_qty);
      if (updatedProduct) {
        console.log("Quantity updated in QuickBooks successfully.", materialData[0]?.name);
      } else {
        console.error("Failed to update quantity in QuickBooks.",materialData[0]?.name);
      }
    }
      let message = `Stock for the product "${materialData[0]?.name}" has been added through a purchase order.`;
      await Product.addClientStockLog({
        catalog_id: row.id,
        catalog_log_message: message,
        timestamp: currentDate,
        status: 1,
        catalog_source: 4,
        added_by: userId,
      });

      // Job Cost Logic for Materials/Catalogs
      if (row.sales_order_id) {
        try {
          console.log("==> updateReceiveOrder Material: Processing sales_order_id: ", row.sales_order_id);
          const projectData = await Project.getProjectByRequestId(row.sales_order_id);
          const projectId = projectData || null;

          const custOrder = await CustomerOrder.getCustomerOrderRequestId(row.sales_order_id);
          const customerOrderId = (custOrder && custOrder.length > 0) ? custOrder[0].id : null;

          let customerOrderProductId = null;
          if (customerOrderId) {
            const newQty = parseFloat(qty);
            const newTotal = parseFloat(row.actual_total_cost || 0);
            const existingCOP = await CustomerOrderProduct.getCustomerOrderProductsbyProduct(row.sales_order_id, row.id);
            if (existingCOP && existingCOP.length > 0) {
              const cop = existingCOP[0];
              await CustomerOrderProduct.updateCustomerOrderProduct({
                product_qty: parseFloat(cop.product_qty) + newQty,
                product_sub_total: parseFloat(cop.product_sub_total) + parseFloat(row.unit_cost) * newQty,
                product_total: parseFloat(cop.product_total) + newTotal,
                jobcost_quantity: parseFloat(cop.jobcost_quantity) + newQty,
                jobcost_total_cost: parseFloat(cop.jobcost_total_cost) + newTotal,
              }, cop.id);
              customerOrderProductId = cop.id;
            } else {
              let requestProduct = {
                order_id: customerOrderId,
                product_name: materialData && materialData.length > 0 ? materialData[0].name : (row?.name || ""),
                product_desc: "",
                product_price: parseFloat(row.unit_cost),
                product_qty: newQty,
                product_sub_total: parseFloat(row.unit_cost) * newQty,
                product_total: newTotal,
                materialInvId: row.material_inventory_id,
                product_id: row.id,
                material: row?.meterial == 1 ? 1 : 0,
                rfp: row?.meterial == 1 ? 0 : 1,
                status: 1,
                timestamp: currentDate,
                jobcost_quantity: newQty,
                jobcost_total_cost: newTotal,
                taken: 1
              };
              customerOrderProductId = await CustomerOrderProduct.createCustomerOrder(requestProduct);
            }
          }

            if (projectId) {
              const newActualQty = parseFloat(qty);
              const newActualTotal = parseFloat(row.actual_total_cost || 0);
              let jobCostData = {
                product_id: row.material_inventory_id,
                project_id: projectId,
                quantity: newActualQty,
                actual_quantity: newActualQty,
                unit_price: parseFloat(row.unit_cost),
                actual_unit_price: parseFloat(row.unit_cost),
                total: newActualTotal,
                actual_total: newActualTotal,
                recipe: 0,
                recipe_name: null,
                timestamp: currentDate,
                added_by: userId,
                rfp: row?.meterial == 1 ? 0 : 1,
                customer_order_product_id: customerOrderProductId
              };
              await JobCost.addJobCost(jobCostData);
            await Project.updateProject(projectId, { jobcost: 1 });
          }
          await Request.updateRequets(row.sales_order_id, { timestamp: currentDate });
        } catch (err) {
          console.log("Error adding material to job cost sales order: ", err);
        }
      }
    }

    // Job Cost Logic for Services
    const currentOrderData = await Product.getOrderById(orderId, true);
    const mainSalesOrderId = (currentOrderData && currentOrderData.length > 0) ? currentOrderData[0].sales_order_id : null;

    const serviceData = await Product.getOrderServices(orderId);
    for (const row of serviceData) {
      if (mainSalesOrderId) {
        const salesOrderId = mainSalesOrderId;
        try {
          console.log("==> updateReceiveOrder Service: Processing sales_order_id: ", salesOrderId);
          const newServiceCost = parseFloat(row.price);
          const existingRS = await Request.getRequestServiceByServiceId(salesOrderId, row.service_id);
          if (existingRS) {
            await Request.updateRequestServices({
              unit_cost: parseFloat(existingRS.unit_cost) + newServiceCost,
              total_cost: parseFloat(existingRS.total_cost) + newServiceCost,
              jobcost_unit_cost: parseFloat(existingRS.jobcost_unit_cost) + newServiceCost,
              jobcost_total_cost: parseFloat(existingRS.jobcost_total_cost) + newServiceCost,
            }, existingRS.id, salesOrderId);
          } else {
            let requestServiceData = {
              request_id: salesOrderId,
              labour_cost_service_id: row.service_id,
              qty: 1,
              unit_cost: newServiceCost,
              total_cost: newServiceCost,
              jobcost_quantity: 1,
              jobcost_unit_cost: newServiceCost,
              jobcost_total_cost: newServiceCost,
              taken: 1,
              status: 1,
              timestamp: currentDate,
              makeAsTask: 0
            };
            await Request.addRequestServices(requestServiceData);
          }

          const projectId = await Project.getProjectByRequestId(salesOrderId);
          if (projectId) {
            await Project.updateProject(projectId, { jobcost: 1 });
          }
          await Request.updateRequets(salesOrderId, { timestamp: currentDate });
        } catch (err) {
          console.log("Error adding service to request services: ", err);
        }
      }
    }

    if (updated) {
      const orderData = await Product.getOrderById(orderId, true);
      // console.log('orderData---------',orderData);
      if (orderData) {
        // Notification
        if (data.status === 1) {
          const finishedProductData = await Product.getOrderById(orderId);
          if (finishedProductData && finishedProductData?.length > 0) {
            for (const fproduct of finishedProductData) {
              const finishedProductsByUnitAndVendor = await Product.getFinishedProductsByUnitAndVendor(fproduct?.vendor_id, fproduct?.unit, orderId, fproduct?.material_id);
              // if(finishedProductsByUnitAndVendor && finishedProductsByUnitAndVendor?.length > 0){
              //   // const deletedOrderProducts = await Product.deleteFinishedProductsByOrderAndMaterial(orderId, fproduct?.id);
              //   const updateMaterialData = {
              //     status : 0, 
              //     qty: 0,
              //   };
              //   const deletedOrderProducts = await Product.updateFinishedProduct(fproduct.id, updateMaterialData)
              //   console.log('fproduct.id', fproduct.id)
              //   console.log('deletedOrderProducts', deletedOrderProducts)

              //   if (deletedOrderProducts) {
              //     const selectedMaterial = finishedProductsByUnitAndVendor[0];
              //     const highestPrice = Math.max(fproduct?.unit_cost || 0, selectedMaterial?.unit_cost || 0);
              //     const selectedQty = parseFloat(selectedMaterial?.qty) || 0;
              //     const fproductQty = parseFloat(fproduct?.qty) || 0;
              //     const totalQty = selectedQty + fproductQty;
              //     console.log('totalQty', totalQty)

              //     const updateData = {
              //       unit_cost: highestPrice || 0, 
              //       qty: totalQty,
              //       total_cost: (highestPrice || 0) * totalQty
              //     };
              //     console.log('updateData', updateData)
              //     console.log('selectedMaterial.id', selectedMaterial.id)

              //     const updateMaterialPrice = await Product.updateFinishedProduct(selectedMaterial.id, updateData)
              //     console.log('updateMaterialPrice', updateMaterialPrice)
              //   }
              // }
            }
          }

          notificationMsg = `The order "${orderData[0].order_name}" has been received successfully.`;
        } else if (data.status === 2) {
          notificationMsg = `Payment received successfully for order "${orderData[0].order_name}"`;
        }
        let notificationUrl = "products";

        let adminIds = await Admin.getAllAdminIds();
        let notificationData = {};
        for (const adminId of adminIds) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
        res.send({ status: 1, message: notificationMsg, orderData: orderData })
      }
    }
    else {
      res.send({ status: 0, message: 'Error updating order' })
    }
  }
  catch (error) {
    console.log("getOrderById error - ", error);
    res.send({ status: 500, message: 'Error', error: error })
  }
}
const addDataSet = async (req, res) => {
  try {
    let { dataSetValue, userId } = req.body
    let currentDate = moment().unix();
    if (!empty(dataSetValue)) {
      console.log(dataSetValue, '----------------dataSetValuedataSetValue')
      let data = {}
      if (!empty(dataSetValue.minVal)) {
        data['min_value'] = parseInt(dataSetValue?.minVal)
      }
      if (!empty(dataSetValue.minVal)) {
        data['max_value'] = parseInt(dataSetValue?.maxVal)
      }
      data['added_by'] = userId
      data['timestamp'] = currentDate
      let result = await Product.updateDataSet(data, 1)
      if (!empty(result)) {
        res.send({
          status: 1, message: 'Updated Product Data set successfully'
        })
      } else {
        res.send({
          status: 0, message: 'Failed to update Product Data set'
        })
      }
    }
  } catch (error) {
    console.log(error)
  }
}
const getDataSet = async (req, res) => {
  try {
    let result = await Product.getDataSet()
    if (!empty(result)) {
      res.send({
        status: 1, result
      })
    }
  } catch (error) {
    console.log(error)
  }
}

////////////////////////////////////////////////QB

const addCategoryToQuickBooks = async (categoryData) => {
  try {
    console.log("Refreshing access token...");
    const token = await getNewAccessToken(); // Ensure you have a valid token

    // Prepare QuickBooks category data
    let quickBooksCategoryData = {
      Name: categoryData.name + locationInventoryCode,
      Type: "Category", // Required for QuickBooks to identify this as a category
    };

    console.log("QuickBooks Category Data:", JSON.stringify(quickBooksCategoryData, null, 2));

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item`;

    const response = await axios.post(apiUrl, quickBooksCategoryData, {
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
        Accept: "application/json",
      },
    });

    if (response.status === 200) {
      console.log("✅ Category has been successfully added to QuickBooks:", response.data);
      return response.data.Item.Id; // Return QuickBooks category ID
    } else {
      console.error("❌ Failed to add the category to QuickBooks:", response.data);
      return null;
    }
  } catch (error) {
    console.error("❌ Error adding category to QuickBooks:", error.response ? error.response.data : error.message);
    return null;
  }
};

const addSubcategoryToQuickBooks = async (subcategoryData) => {
  try {
    console.log("Refreshing access token...");
    const token = await getNewAccessToken(); // Ensure you have a valid token

    // Ensure parent category exists
    if (!subcategoryData.qbCategoryId) {
      console.error("❌ Error: Parent Category (qbCategoryId) is required for subcategories.");
      return null;
    }

    // Prepare QuickBooks subcategory data
    let quickBooksSubcategoryData = {
      Name: subcategoryData.name + locationInventoryCode,
      Type: "Category", // QuickBooks identifies subcategories as categories
      SubItem: true, // Mark this as a subcategory
      ParentRef: { value: String(subcategoryData.qbCategoryId) }, // Reference to parent category
    };

    console.log("QuickBooks Subcategory Data:", JSON.stringify(quickBooksSubcategoryData, null, 2));

    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item`;

    const response = await axios.post(apiUrl, quickBooksSubcategoryData, {
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
        Accept: "application/json",
      },
    });

    if (response.status === 200) {
      console.log("✅ Subcategory has been successfully added to QuickBooks:", response.data);
      return response.data.Item.Id; // Return QuickBooks subcategory ID
    } else {
      console.error("❌ Failed to add the subcategory to QuickBooks:", response.data);
      return null;
    }
  } catch (error) {
    console.error("❌ Error adding subcategory to QuickBooks:", error.response ? error.response.data : error.message);
    return null;
  }
};

const findExistingCategoryInQuickBooks = async (categoryName) => {
  try {
    let token = await getNewAccessToken();
    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query`;

    // Escape single quotes in category name for SQL query
    const escapedName = (categoryName + locationInventoryCode).replace(/'/g, "''");

    const searchResponse = await axios.get(
      apiUrl,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          Accept: 'application/json',
        },
        params: {
          query: `select * from Item where Type='Category' and Name='${escapedName}'`,
          minorversion: 63,
        },
      }
    );

    const category = searchResponse.data.QueryResponse.Item?.[0];

    if (!category) {
      console.log(`Category with name "${categoryName}" not found in QuickBooks.`);
      return null;
    }

    console.log("Category found in QuickBooks:", category);
    return category.Id;
  }
  catch (error) {
    console.error("Error searching for existing category:", error.response?.data || error.message);
    return null;
  }
}

const findExistingSubcategoryInQuickBooks = async (subcategoryName) => {
  try {
    let token = await getNewAccessToken();
    const apiUrl = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query`;

    const escapedName = (subcategoryName + locationInventoryCode).replace(/'/g, "''");

    const searchResponse = await axios.get(
      apiUrl,
      {
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          Accept: 'application/json',
        },
        params: {
          query: `select * from Item where Type='Category' and Name='${escapedName}'`,
          minorversion: 63,
        },
      }
    );

    const subcategory = searchResponse.data.QueryResponse.Item?.[0];

    if (!subcategory) {
      console.log(`Subcategory with name "${subcategoryName}" not found in QuickBooks.`);
      return null;
    }

    console.log("Subcategory found in QuickBooks:", subcategory);
    return subcategory.Id;
  }
  catch (error) {
    console.error("Error searching for existing subcategory:", error.response?.data || error.message);
    return null;
  }
}
///////////////////////////////////////////

const addProductCategory = async (req, reply) => {
  try {
    let productType = {};
    let currentDate = Math.round(moment().valueOf() / 1000);

    productType["name"] = req.body.name;
    productType["timestamp"] = moment().unix();
    productType["added_by"] = req.body.userId;
    productType["category_type"] = req.body?.categoryType?.id;
    productType["markup"] = req.body?.markup || null;
    productType["wholesale_markup"] = req.body?.wholesaleMarkup || null;
    productType["status"] = 1;



    // Check if product category name exists
    let productNameExist = await Product.isProductCategoryExist(productType["name"]);
    if (productNameExist) {
      return reply.send({ status: 0, message: "Product Category already exists." });
    }

    try {
      let qbResult = await addCategoryToQuickBooks(productType)
      let QbCatetoryId = qbResult
      if (QbCatetoryId) {
        productType['qbCategoryId'] = QbCatetoryId
      }
    } catch (error) {
      console.log("error", error)
    }
    // Add product category
    let productTypeId = await Product.addProductCategory(productType);
    if (!productTypeId) {
      return reply.send({ status: 0, message: "Failed to add the product category." });
    }

    let notificationMsg = `New product Category "${productType.name}" has been added.`;
    let notificationUrl = "product";

    // Increment notification count for the user who added the product category
    await Admin.incrementField(productType.added_by, "noti_count", 1);

    let notificationData_user = {
      user_id: productType.added_by,
      url: notificationUrl,
      timestamp: currentDate,
      message: notificationMsg,
      status: 1,
      from_id: productType.added_by,
    };

    // Add notification for the user who added the product category
    await Admin.addNotification(notificationData_user);

    // Notify all admins
    let adminIds = await Admin.getAllAdminIds();
    for (const adminId of adminIds) {
      if (adminId !== productType.added_by) {
        await Admin.incrementField(adminId, "noti_count", 1);
        let notificationData = {
          user_id: adminId,
          url: notificationUrl,
          timestamp: currentDate,
          message: notificationMsg,
          status: 1,
          from_id: productType.added_by,
        };
        await Admin.addNotification(notificationData);
      }
    }

    return reply.send({
      status: 1,
      message: "Product Category has been added successfully!",
    });

  } catch (error) {
    console.error("Error adding product category:", error);
    return reply.send({ status: 500, message: "Server error", error });
  }
};

//sub
const addProductSubcategory = async (req, reply) => {
  try {
    let productType = {};
    let currentDate = Math.round(moment().valueOf() / 1000);

    productType["name"] = req.body.name;
    productType["category_id"] = req.body.product_category?.c_id;
    productType["qbCategoryId"] = req.body.product_category?.qbCategoryId || null;
    productType["timestamp"] = moment().unix();
    productType["added_by"] = req.body.userId;
    productType["status"] = 1;
    productType["markup"] = req.body?.markup || null;
    productType["wholesale_markup"] = req.body?.wholesaleMarkup || null;

    // Check if the product subcategory already exists
    let productNameExist = await Product.isProductSubCategoryExist(productType["name"]);
    if (productNameExist) {
      return reply.send({ status: 0, message: "Product Sub Category already exists." });
    }

    const subcategoryData = {
      name: productType.name,     // "New Subcategory", // Subcategory Name
      qbCategoryId: productType.qbCategoryId
    };

    try {
      let qbResult = await await addSubcategoryToQuickBooks(subcategoryData)
      let QbSubCatetoryId = qbResult
      if (QbSubCatetoryId) {
        productType['qbSubCategoryId'] = QbSubCatetoryId
      }
    } catch (error) {
      console.log("error", error)
    }

    // Add the product subcategory
    let productTypeId = await Product.addProductSubCategory(productType);
    if (!productTypeId) {
      return reply.send({ status: 0, message: "Failed to add the product subcategory." });
    }

    let notificationMsg = `New product Sub Category "${productType.name}" has been added.`;
    let notificationUrl = "product";

    // Increment notification count for the user who added the product subcategory
    await Admin.incrementField(productType.added_by, "noti_count", 1);

    let notificationData_user = {
      user_id: productType.added_by,
      url: notificationUrl,
      timestamp: currentDate,
      message: notificationMsg,
      status: 1,
      from_id: productType.added_by,
    };

    // Add notification for the user who added the product subcategory
    await Admin.addNotification(notificationData_user);

    // Notify all admins
    let adminIds = await Admin.getAllAdminIds();
    for (const adminId of adminIds) {
      if (adminId !== productType.added_by) {
        await Admin.incrementField(adminId, "noti_count", 1);
        let notificationData = {
          user_id: adminId,
          url: notificationUrl,
          timestamp: currentDate,
          message: notificationMsg,
          status: 1,
          from_id: productType.added_by,
        };
        await Admin.addNotification(notificationData);
      }
    }

    return reply.send({
      status: 1,
      message: "Product Sub Category has been added successfully!",
    });

  } catch (error) {
    console.error("Error adding product subcategory:", error);
    return reply.send({ status: 500, message: "Server error", error });
  }
};



const editProductCategory = async (req, reply) => {
  try {
    let productType = {};
    let currentDate = Math.round(moment().valueOf() / 1000);
    let productTypeId = req.body.id;

    productType["name"] = req.body.name;
    productType["category_type"] = req.body?.categoryType?.id;
    // productType["markup"] = req.body?.markup || null;
    // productType["wholesale_markup"] = req.body?.wholesaleMarkup || null;

    const existingData = await Product.findProductCategoryById(productTypeId)
    console.log('existingData', existingData)

    if (!existingData.qbCategoryId || existingData.qbCategoryId == null) {
      let updateCategoryQb = await addCategoryToQuickBooks(productType)
      productType["qbCategoryId"] = updateCategoryQb
      if (!updateCategoryQb) {
        let result = await findExistingCategoryInQuickBooks(productType.name)
        if (result) {
          productType["qbCategoryId"] = result
        }
      }
    }
    // Check if the product category already exists
    let productNameExist = await Product.isProductCategoryExist(productType["name"], productTypeId);
    if (productNameExist) {
      return reply.send({ status: 0, message: "Product Category name already exists." });
    }

    // Update the product category
    let productCategory = await Product.updateProductCategory(productType, productTypeId);
    if (!productCategory) {
      return reply.send({ status: 0, message: "Failed to update product Category." });
    }

    // Fetch all products with the updated category
    let allProductsWithCategory = await Product.fetchAllProductsByCategory(true, false, productTypeId);
    console.log("#################ProductCategory", allProductsWithCategory);

    // Update the price and retail price for each product
    for (let i = 0; i < allProductsWithCategory.length; i++) {
      let cost = allProductsWithCategory[i].expected_material_cost + allProductsWithCategory[i].expected_total_service_cost;
      let product = {};
      product.price = cost + (cost * productType["wholesale_markup"] / 100);
      product.retail_price = cost + (cost * productType["markup"] / 100);
      await Product.updateProduct(allProductsWithCategory[i].id, product);
    }

    // Send notifications
    let notificationMsg = `Product Category "${productType.name}" has been updated.`;
    let notificationUrl = "products/product-types";

    // Increment notification count for the user who added the product category
    await Admin.incrementField(req.body.added_by, "noti_count", 1);

    let notificationData_user = {
      user_id: req.body.added_by,
      url: notificationUrl,
      timestamp: currentDate,
      message: notificationMsg,
      status: 1,
      from_id: req.body.added_by,
    };

    // Add notification for the user who added the product category
    await Admin.addNotification(notificationData_user);

    // Notify all admins
    let adminIds = await Admin.getAllAdminIds();
    for (const adminId of adminIds) {
      if (adminId !== req.body.added_by) {
        await Admin.incrementField(adminId, "noti_count", 1);
        let notificationData = {
          user_id: adminId,
          url: notificationUrl,
          timestamp: currentDate,
          message: notificationMsg,
          status: 1,
          from_id: req.body.added_by,
        };
        await Admin.addNotification(notificationData);
      }
    }

    return reply.send({
      status: 1,
      message: "Product Category has been updated successfully!",
    });
  } catch (error) {
    console.error("Error updating product category:", error);
    return reply.send({ status: 500, message: "Server error", error });
  }
};

//sub
const editProductSubCategory = async (req, reply) => {
  try {
    let productType = {};
    let currentDate = Math.round(moment().valueOf() / 1000);
    let productTypeId = req.body.id;
    console.log('req.body***', req.body)

    productType["name"] = req.body.name;
    productType["category_id"] = req.body.product_category?.c_id;
    productType["markup"] = req.body?.markup || null;
    productType["wholesale_markup"] = req.body?.wholesaleMarkup || null;

    const existingCatData = await Product.findProductCategoryById(productType.category_id)
    console.log('existingCatData', existingCatData)

    const existingData = await Product.findProductSubCategoryById(productTypeId)
    console.log('existingData', existingData)

    if (!existingData.qbSubCategoryId || existingData.qbSubCategoryId == null) {
      productType["qbCategoryId"] = existingCatData.qbCategoryId
      let updateCategoryQb = await addSubcategoryToQuickBooks(productType)
      productType["qbSubCategoryId"] = updateCategoryQb

      if (!updateCategoryQb) {
        let result = await findExistingSubcategoryInQuickBooks(productType.name)
        if (result) {
          productType["qbSubCategoryId"] = result
        }
      }
    }

    // Check if the product subcategory already exists
    let productNameExist = await Product.isProductSubCategoryExist(productType["name"], productTypeId);
    if (productNameExist) {
      return reply.send({ status: 0, message: "Product Sub Category name already exists." });
    }

    // Update the product subcategory
    let productCategory = await Product.updateProductSubCategory(productType, productTypeId);
    if (!productCategory) {
      return reply.send({ status: 0, message: "Failed to update product sub Category." });
    }

    // // ⬇️ Propagate new subcategory markup to its material products
    // if (productType?.markup != null) {
    //   const scId = Array.isArray(productTypeId)
    //     ? productTypeId.filter(v => v != null).map(Number)
    //     : Number(productTypeId);

    //   if (scId && (!Array.isArray(scId) || scId.length)) {
    //     const products = await Product.getMaterialProductsBySubCategory(scId);
    //     console.log('products******', products)
    //     const newMarkup = Number(req.body?.markup ?? productType.markup);
    //     if (Number.isFinite(newMarkup)) {
    //       for (const p of products) {
    //         await Product.updateProduct(p.id, { retail_price: newMarkup });
    //         console.log('newMarkup', newMarkup)
    //       }
    //     }
    //   }
    // }

    // ⬇️ Propagate new subcategory markup + recompute recipes/products that use its materials
    if (req.body?.markup != null || req.body?.wholesaleMarkup != null) {
      const scId = Array.isArray(productTypeId)
        ? productTypeId.filter(v => v != null).map(Number)
        : Number(productTypeId);

      if (scId && (!Array.isArray(scId) || scId.length)) {
        const materials = await Product.getMaterialProductsBySubCategory(scId);

        if (req.body?.markup != null) {
          const newMarkup = Number(req.body.markup);
          if (Number.isFinite(newMarkup)) {
            for (const m of materials) {
              await Product.updateProduct(m.id, { retail_price: newMarkup });
            }
          }
        }

        for (const m of materials) {
          let allRecipies = await Product.fetchRecipesBasedOnMaterial(m.id);
          for (let j = 0; j < allRecipies.length; j++) {
            let getRecipe = await Recipe.getRecipeByIdForAutoUpdate(allRecipies[j].recipe_id);

            let expectedMaterialCost = getRecipe[0]?.ingredients?.reduce((acc, cur) => {
              return acc + (cur?.highest_unit_cost * cur?.quantity);
            }, 0) || 0;

            // match updateProduct logic
            const totals = (getRecipe[0]?.ingredients || []).reduce(
              (acc, ing) => {
                const qty = Number(ing?.quantity ?? 0);
                const unitCost = Number(ing?.highest_unit_cost ?? ing?.materialCost ?? 0);
                const markupPct = Number(ing?.markup ?? 0);
                const lineSub = unitCost * qty;
                const lineRetail = (lineSub * markupPct) / 100;
                acc.subTotal += lineSub;
                acc.retail += lineRetail;
                acc.total += lineSub + lineRetail;
                return acc;
              },
              { subTotal: 0, retail: 0, total: 0 }
            );

            const expectedMaterialCostWithMarkup = Number(totals.total.toFixed(2));

            const expectedTotalServiceCost =
              Number(getRecipe?.[0]?.expected_total_service_cost) ||
              (getRecipe?.[0]?.services || []).reduce((sum, s) => {
                const amt = Number(s?.serviceAmount) || 0;
                const qty = Number(s?.quantity) || 0;
                return sum + amt * qty;
              }, 0);

            let expectedCost = expectedMaterialCost + getRecipe[0].expected_service_cost;
            let data = {
              expected_cost: expectedCost,
              expected_material_cost: expectedMaterialCost,
              expected_material_cost_with_markup: expectedMaterialCostWithMarkup,
              expected_cost_with_markup: Number(expectedMaterialCostWithMarkup) + Number(expectedTotalServiceCost),
            };
            await Recipe.updateRecipe(data, getRecipe[0].id);

            // ---------- UPDATE PRODUCTS tied to this recipe (also sets formula_cost) ----------
            const allProductsWithCategory = await Product.fetchAllProductsByRecipe(true, false, getRecipe[0].id);

            for (let k = 0; k < allProductsWithCategory.length; k++) {
              const p = allProductsWithCategory[k];

              const emc = Number(p.expected_material_cost || 0);
              const ets = Number(p.expected_total_service_cost || 0);
              const baseCost = emc + ets;

              const wholesalePct = Number(p.wholesaleMarkup || 0);
              const retailPct = Number(p.retailMarkup || 0);

              await Product.updateProduct(p.id, {
                price: baseCost + (emc * wholesalePct) / 100,
                // retail_price: baseCost + (emc * retailPct) / 100,
                special_price: emc,
                // same formula cost rule as elsewhere: EMC-with-markup + service
                formula_cost: Number(expectedMaterialCostWithMarkup) + Number(expectedTotalServiceCost),
              });
            }

            // const refreshedRecipe = await Recipe.getRecipeById(getRecipe[0].id);
            // const emcWithMarkup = Number(refreshedRecipe?.[0]?.expected_material_cost_with_markup) || 0;

            // const catalogProducts = await Product.catalogByReceipte(getRecipe[0].id);
            // for (const cat of catalogProducts) {
            //   const prevSpecial = Number(cat.special_price || 0);
            //   const prevFormula = Number(cat.formula_cost || 0);
            //   const service_cost = prevFormula - prevSpecial;

            //   const newSpecial = emcWithMarkup;
            //   const newFormula = service_cost + newSpecial;

            //   await Product.updateProduct(cat.id, {
            //     special_price: newSpecial,
            //     formula_cost: newFormula,
            //   });
            // } uncomment this if cause any problem
          }
        }
      }
    }

    // Send notifications
    let notificationMsg = `Product Sub Category "${productType.name}" has been updated.`;
    let notificationUrl = "products/product-types";

    // Increment notification count for the user who added the product subcategory
    await Admin.incrementField(req.body.added_by, "noti_count", 1);

    let notificationData_user = {
      user_id: req.body.added_by,
      url: notificationUrl,
      timestamp: currentDate,
      message: notificationMsg,
      status: 1,
      from_id: req.body.added_by,
    };

    // Add notification for the user who added the product subcategory
    await Admin.addNotification(notificationData_user);

    // Notify all admins
    let adminIds = await Admin.getAllAdminIds();
    for (const adminId of adminIds) {
      if (adminId !== req.body.added_by) {
        await Admin.incrementField(adminId, "noti_count", 1);
        let notificationData = {
          user_id: adminId,
          url: notificationUrl,
          timestamp: currentDate,
          message: notificationMsg,
          status: 1,
          from_id: req.body.added_by,
        };
        await Admin.addNotification(notificationData);
      }
    }

    return reply.send({
      status: 1,
      message: "Product Sub Category has been updated successfully!",
    });
  } catch (error) {
    console.error("Error updating product subcategory:", error);
    return reply.send({ status: 500, message: "Server error", error });
  }
};

const getAllProductCategories = async (request, reply) => {
  let vendorData;
  if (request.body) {
    const { userType, userId, isVendor, page, rowPerpage, search } = request.body
    if ((userType && userId) || isVendor) {
      vendorData = await Client.getAllClients(true, userType, userId, page, rowPerpage, search, isVendor);
    }
  }
  let productCategories = await Product.getAllProductCategories(true);
  for (const data of productCategories) {
    let subData = await Product.getSubCatFromCategoryId(data.id)
    let subArray = []
    for (const ss of subData) {
      subArray.push(ss.name)
    }
    data.sub_category = subArray
  }

  reply.send({
    status: 1,
    productCategories,
    vendorData,
    message: "Data fetched successfully",
  });
};
//sub
const getAllProductSubCategories = async (request, reply) => {
  let data = await Product.getAllProductSubCategories(true);
  reply.send({
    status: 1,
    data,
    message: "Data fetched successfully",
  });
};
const deleteProductCategory = async (request, reply) => {
  try {
    let { id, status } = request.body;
    let currentDate = Math.round(moment().valueOf() / 1000);
    if (!empty(id)) {
      let productType = await Product.getProductCategoryById(id);
      console.log(request.body, '------------------request.body')
      if (status == 2) {
        let deleted = await Product.deleteProductCategory(id);

        if (deleted) {
          // // Notification
          let notificationMsg = `The Product Category  "${productType.name}" has been deleted.`;
          let notificationUrl = "product";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};

          for (const adminId of adminIds) {
            const incrementResult = await Admin.incrementField(
              adminId,
              "noti_count",
              1
            );

            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: productType.added_by,
            };

            const addNotificationResult = await Admin.addNotification(
              notificationData
            );
          }
          reply.send({
            status: 1,
            message: `The product type "${productType.name}" has been deleted successfully!`,
          });
        } else {
          reply.send({ status: 0, message: `Unable to delete the product!` });
        }
      } else if (status == 1) {
        let deleted = await Product.updateProductCategory(
          { status: status },
          id
        );

        if (deleted) {
          // // Notification
          let notificationMsg = `The project "${productType.name}" has been made active.`;
          let notificationUrl = "products";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};

          for (const adminId of adminIds) {
            const incrementResult = await Admin.incrementField(
              adminId,
              "noti_count",
              1
            );

            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: productType.added_by,
            };

            const addNotificationResult = await Admin.addNotification(
              notificationData
            );
          }
          reply.send({
            status: 2,
            message: `The Product "${productType.name}" has been made active!`,
          });
        } else {
          reply.send({ status: 3, message: "Unable to activate the product!" });
        }
      } else if (status == 0) {
        let deleted = await Product.updateProductCategory(
          { status: status },
          id
        );

        if (deleted) {
          // Notification
          let notificationMsg = `The Product "${productType.name}" has been made inactive.`;
          let notificationUrl = "products";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};

          for (const adminId of adminIds) {
            const incrementResult = await Admin.incrementField(
              adminId,
              "noti_count",
              1
            );

            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: productType.added_by,
            };

            const addNotificationResult = await Admin.addNotification(
              notificationData
            );
          }

          reply.send({
            status: 4,
            message: `The product "${productType.name}" has been made Inactive!`,
          });
        } else {
          reply.send({
            status: 5,
            message: "Unable to deactivate the product!",
          });
        }
      }
    } else {
      console.log("Error................");
      reply.send({ status: 500, message: "Error!" });
    }
  } catch (error) {
    reply.status(500).send({ message: "Server error" + error });
  }
};
//sub
const deleteProductSubCategory = async (request, reply) => {
  try {
    let { id, status } = request.body;
    let currentDate = Math.round(moment().valueOf() / 1000);
    if (!empty(id)) {
      let productType = await Product.getProductSubCategoryById(id);
      if (status == 2) {
        let deleted = await Product.deleteProductSubCategory(id);

        if (deleted) {
          // // Notification
          let notificationMsg = `The Product Sub Category  "${productType.name}" has been deleted.`;
          let notificationUrl = "product";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};

          for (const adminId of adminIds) {
            const incrementResult = await Admin.incrementField(
              adminId,
              "noti_count",
              1
            );

            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: productType.added_by,
            };

            const addNotificationResult = await Admin.addNotification(
              notificationData
            );
          }
          reply.send({
            status: 1,
            message: `The Product Sub Category "${productType.name}" has been deleted successfully!`,
          });
        } else {
          reply.send({ status: 0, message: `Unable to delete the product!` });
        }
      } else if (status == 1) {
        let deleted = await Product.updateProductSubCategory(
          { status: status },
          id
        );

        if (deleted) {
          // // Notification
          let notificationMsg = `The Product Sub Category "${productType.name}" has been made active.`;
          let notificationUrl = "products";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};

          for (const adminId of adminIds) {
            const incrementResult = await Admin.incrementField(
              adminId,
              "noti_count",
              1
            );

            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: productType.added_by,
            };

            const addNotificationResult = await Admin.addNotification(
              notificationData
            );
          }
          reply.send({
            status: 2,
            message: `The Product Sub Category "${productType.name}" has been made active!`,
          });
        } else {
          reply.send({ status: 3, message: "Unable to activate the product!" });
        }
      } else if (status == 0) {
        let deleted = await Product.updateProductSubCategory(
          { status: status },
          id
        );

        if (deleted) {
          // Notification
          let notificationMsg = `The Product "${productType.name}" has been made inactive.`;
          let notificationUrl = "products";

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};

          for (const adminId of adminIds) {
            const incrementResult = await Admin.incrementField(
              adminId,
              "noti_count",
              1
            );

            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: productType.added_by,
            };

            const addNotificationResult = await Admin.addNotification(
              notificationData
            );
          }

          reply.send({
            status: 4,
            message: `The product "${productType.name}" has been made Inactive!`,
          });
        } else {
          reply.send({
            status: 5,
            message: "Unable to deactivate the product!",
          });
        }
      }
    } else {
      console.log("Error................");
      reply.send({ status: 500, message: "Error!" });
    }
  } catch (error) {
    reply.status(500).send({ message: "Server error" + error });
  }
};
const getSubCategoryById = async (req, res) => {
  try {
    let data = await Product.getProductSubCategoryById(req.body.id)
    if (!empty(data)) {
      res.send({
        status: 1, data
      })
    }
  } catch (error) {
    console.log(error)
  }
}

const getSubcategoryFromCategoryId = async (req, res) => {
  try {
    let { id } = req.body
    let data = await Product.getSubCatFromCategoryId(id)
    if (data) {
      res.send({
        status: 1, data
      })
    } else {
      res.send({
        status: 0
      })
    }
  } catch (error) {
    console.log(error)
  }
}

///////////////////////////////////quikbook functin

let serviceUniqueId;
const addServiceToQuickBooks = async (serviceData) => {
  let token = accessToken;

  const postService = async (useNewToken = true) => {
    if (useNewToken) {
      console.log('Refreshing access token...');
      token = await getNewAccessToken();
    }


    // const incomeAccounts = await getAccounts('Income');
    // const expenseAccounts = await getAccounts('Cost of Goods Sold'); // Fetch expense accounts
    // const dynamicServiceExpAccIdNew = expenseAccounts?.length > 0 ? expenseAccounts[0].Id : serviceExpAccId;
    let dynamicServiceIncAccId = null //await findAccountIdByAcctNum(4010000) //|| incomeAccounts?.length > 0 ? incomeAccounts[0].Id : serviceIncAccId; 
    let dynamicServiceExpAccIdNew = null // await findAccountIdByAcctNum(5041000) //|| expenseAccounts?.length > 0 ? expenseAccounts[0].Id : serviceExpAccId;
    if (serviceData?.qb_income_account) {
      dynamicServiceIncAccId = await findAccountIdByAcctNum(serviceData?.qb_income_account)
    } else {
      dynamicServiceIncAccId = await findAccountIdByAcctNum(4010000)
    }
    if (serviceData?.qb_expense_account) {
      dynamicServiceExpAccIdNew = await findAccountIdByAcctNum(serviceData?.qb_expense_account)
    } else {
      dynamicServiceExpAccIdNew = await findAccountIdByAcctNum(5041000)
    }
    try {

      let productName = serviceData.name.replace(/[:\t\n\r]+/g, "-").trim();
      if (productName.length > 95) {
        productName = productName.substring(0, 95);
      }

      const quickBooksServiceData = {
        // Name: serviceData.name + locationInventoryCode,
        Name: productName + locationInventoryCode,
        Type: "Service",
        IncomeAccountRef: {
          value: dynamicServiceIncAccId   //|| serviceIncAccId
        },
        ExpenseAccountRef: {
          value: dynamicServiceExpAccIdNew //|| serviceExpAccId
        },
        SalesTaxIncluded: false,
        PurchaseTaxIncluded: false,
        UnitPrice: serviceData.total_cost,
        PurchaseCost: serviceData.cost,
        // QtyOnHand: 1, 
      };

      console.log("QuickBooks Service Data:", JSON.stringify(quickBooksServiceData, null, 2));

      const apiUrl = PRODUCTION
        ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item?minorversion=63`
        : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item?minorversion=63`;


      const response = await axios.post(
        apiUrl,
        quickBooksServiceData,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
            Accept: 'application/json'
          }
        }
      );

      if (response.status === 200) {
        console.log("Service has been successfully added to QuickBooks:", response.data);
        serviceUniqueId = response.data.Item.Id;
      } else {
        console.error("Failed to add the service to QuickBooks:", response.data);
        serviceUniqueId = null;
      }
    } catch (error) {
      serviceUniqueId = null;
      console.error("Error adding service to QuickBooks:", error.response ? error.response.data : error.message);
      const faultType = error.response?.data?.Fault?.type;
      if (faultType === 'AUTHENTICATION') {
        console.log('Access token expired, refreshing token...');
        try {
          await postService(true);
        } catch (refreshError) {
          serviceUniqueId = null;
          console.error('Error refreshing token:', refreshError.response?.data || refreshError.message);
        }
      } else if (faultType === 'ValidationFault') {
        serviceUniqueId = null;
        console.error("Validation error adding service to QuickBooks:", JSON.stringify(error.response?.data, null, 2));
      } else {
        serviceUniqueId = null;
        console.error("Other error adding service to QuickBooks:", error.response?.data || error.message);
      }
    }
  };

  await postService();
};

// const updateServiceInQuickBooks = async (serviceData) => {
//   let token = accessToken;

//   const postUpdate = async (useNewToken = true) => {
//     if (useNewToken) {
//       console.log('Refreshing access token...');
//       token = await getNewAccessToken();
//     }

//     try {
//       const apiUrl = PRODUCTION
//       ? `https://quickbooks.api.intuit.com/v3/company/${realmId}`
//       : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;
//       // Step 1: Search for the service by name
//       const searchResponse = await axios.get(
//         `${apiUrl}/query?query=select * from Item where Name='${serviceData.existingName}'&minorversion=63`,
//         {
//           headers: {
//             Authorization: `Bearer ${token}`,
//             'Content-Type': 'application/json',
//             Accept: 'application/json'
//           }
//         }
//       );

//       if (searchResponse.status === 200 && searchResponse.data.QueryResponse.Item && searchResponse.data.QueryResponse.Item.length > 0) {
//         const existingService = searchResponse.data.QueryResponse.Item[0];

//         // Step 2: Update the service data
//         const quickBooksServiceData = {
//           ...existingService, // Include the existing service details
//           Name: serviceData.name,
//           Type: "Service",
//           IncomeAccountRef: {
//             value: PRODUCTION? "5" : "122"  // "79" 
//           },
//           ExpenseAccountRef: {
//             value: PRODUCTION? "2" : "132"  // "80" 
//           },
//           SalesTaxIncluded: false,
//           PurchaseTaxIncluded: false,
//           UnitPrice: serviceData.total_cost,
//           sparse: true // Perform a partial update
//         };

//         console.log("QuickBooks Service Data for Update:", JSON.stringify(quickBooksServiceData, null, 2));

//         // Step 3: Make the update request
//         const updateResponse = await axios.post(
//          `${apiUrl}/item?operation=update&minorversion=63`,
//           quickBooksServiceData,
//           {
//             headers: {
//               Authorization: `Bearer ${token}`,
//               'Content-Type': 'application/json',
//               Accept: 'application/json'
//             }
//           }
//         );

//         if (updateResponse.status === 200) {
//           console.log("Service has been successfully updated in QuickBooks:", updateResponse.data);
//           serviceUniqueId = updateResponse.data.Item.Id;
//         } else {
//           console.error("Failed to update the service in QuickBooks:", updateResponse.data);
//         }
//       } else {
//         console.log(`Service with name '${serviceData.name}' not found in QuickBooks.`);
//       }
//     } catch (error) {
//       console.error("Error updating service in QuickBooks:", error.response ? error.response.data : error.message);
//       const faultType = error.response?.data?.Fault?.type;
//       if (faultType === 'AUTHENTICATION') {
//         console.log('Access token expired, refreshing token...');
//         try {
//           await postUpdate(true);
//         } catch (refreshError) {
//           console.error('Error refreshing token:', refreshError.response?.data || refreshError.message);
//         }
//       } else if (faultType === 'ValidationFault') {
//         console.error("Validation error updating service in QuickBooks:", JSON.stringify(error.response?.data, null, 2));
//       } else {
//         console.error("Other error updating service in QuickBooks:", error.response?.data || error.message);
//       }
//     }
//   };

//   await postUpdate();
// };



////////////////////////////////////////////


const updateServiceInQuickBooks = async (serviceData) => {
  let token = accessToken;

  const postUpdate = async (useNewToken = true) => {
    if (useNewToken) {
      console.log('Refreshing access token...');
      token = await getNewAccessToken();
    }

    try {
      const searchLink = PRODUCTION
        ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query?query=select * from Item where Name='${serviceData.existingName + locationInventoryCode}'`
        : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query?query=select * from Item where Name='${serviceData.existingName + locationInventoryCode}'`;

      // Step 1: Search for the service by name
      const searchResponse = await axios.get(
        searchLink,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
            Accept: 'application/json'
          }
        }
      );

      if (searchResponse.status === 200 && searchResponse.data.QueryResponse.Item && searchResponse.data.QueryResponse.Item.length > 0) {
        const existingService = searchResponse.data.QueryResponse.Item[0];

        let productName = serviceData.name.replace(/[:\t\n\r]+/g, "-").trim();
        if (productName.length > 95) {
          productName = productName.substring(0, 95);
        }
        let dynamicServiceIncAccId = null;
        let dynamicServiceExpAccIdNew = null // await findAccountIdByAcctNum(5041000) 
        if (serviceData?.qb_income_account) {
          dynamicServiceIncAccId = await findAccountIdByAcctNum(serviceData?.qb_income_account)
        } else {
          dynamicServiceIncAccId = await findAccountIdByAcctNum(4010000)
        }
        if (serviceData?.qb_expense_account) {
          dynamicServiceExpAccIdNew = await findAccountIdByAcctNum(serviceData?.qb_expense_account)
        } else {
          dynamicServiceExpAccIdNew = await findAccountIdByAcctNum(5041000)
        }

        // Step 2: Update the service data (without changing account refs)
        const quickBooksServiceData = {
          ...existingService, // Keep existing data
          // Name: serviceData.name + locationInventoryCode,
          Name: productName + locationInventoryCode,
          Type: "Service",
          UnitPrice: serviceData.total_cost,
          PurchaseCost: serviceData.cost,
          SalesTaxIncluded: false,
          PurchaseTaxIncluded: false,
          IncomeAccountRef: {
            value: dynamicServiceIncAccId
          },
          ExpenseAccountRef: {
            value: dynamicServiceExpAccIdNew
          },
          sparse: true // Perform a partial update
        };

        console.log("QuickBooks Service Data for Update:", JSON.stringify(quickBooksServiceData, null, 2));

        const updateLink = PRODUCTION
          ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/item?operation=update&minorversion=63`
          : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/item?operation=update&minorversion=63`;

        // Step 3: Make the update request
        const updateResponse = await axios.post(
          updateLink,
          quickBooksServiceData,
          {
            headers: {
              Authorization: `Bearer ${token}`,
              'Content-Type': 'application/json',
              Accept: 'application/json'
            }
          }
        );

        if (updateResponse.status === 200) {
          console.log("Service has been successfully updated in QuickBooks:", updateResponse.data);
          serviceUniqueId = updateResponse.data.Item.Id;
        } else {
          console.error("Failed to update the service in QuickBooks:", updateResponse.data);
        }
      } else {
        console.log(`Service with name '${serviceData.existingName}' not found in QuickBooks.`);
      }
    } catch (error) {
      console.error("Error updating service in QuickBooks:", error.response ? error.response.data : error.message);
      const faultType = error.response?.data?.Fault?.type;
      if (faultType === 'AUTHENTICATION') {
        console.log('Access token expired, refreshing token...');
        try {
          await postUpdate(true);
        } catch (refreshError) {
          console.error('Error refreshing token:', refreshError.response?.data || refreshError.message);
        }
      } else if (faultType === 'ValidationFault') {
        console.error("Validation error updating service in QuickBooks:", JSON.stringify(error.response?.data, null, 2));
      } else {
        console.error("Other error updating service in QuickBooks:", error.response?.data || error.message);
      }
    }
  };

  await postUpdate();
};


const addLabourCostServices = async (req, res) => {
  try {
    serviceUniqueId = null
    let quickbookError = null
    let currentDate = Math.round(moment().valueOf() / 1000);
    let deptData = {};
    deptData['name'] = req.body.name;
    deptData['description'] = req.body.description;
    deptData['markup'] = req.body.markup;
    deptData['service_type'] = req.body.type.id;
    deptData['total_cost'] = req.body.total_cost || 0;
    deptData['cost'] = req.body.cost || 0;
    deptData['added_by'] = req.body.userId;
    deptData['inhouse'] = Number(req.body.inHouse ?? 0);
    deptData['originInhouse'] = Number(req.body.inHouse ?? 0);
    deptData['workflow'] = req.body.workflow
    deptData['timestamp'] = currentDate;
    deptData['avatarUrl'] = req.body.avatarUrl;
    deptData['makeAsTask'] = req.body.makeAsTask ?? 1;
    deptData['makeInstallCard'] = req.body.makeInstallCard ?? 1;
    deptData['jobCost'] = req.body.jobCost ?? 1;
    deptData['taxable'] = req.body.taxable || 0;
    deptData['status'] = 1;
    deptData['taxCodeId'] = req.body.taxCodeId || null
    deptData['taxCode'] = req.body.taxCode || null
    deptData['isNew'] = 1
    deptData['qb_income_account'] = req.body?.qb_income_account?.glCode || null
    deptData['qb_expense_account'] = req.body?.qb_expense_account?.glCode || null

    let productNameExist = await Product.isProductNameExist(deptData.name);
    let isServiceNameExist = await Product.isServiceNameExist(deptData.name);
    if (productNameExist != true && isServiceNameExist != true) {
      ///////////////////////////////////////////qb
      try {
        await addServiceToQuickBooks(deptData);
        if (serviceUniqueId) {
          deptData.syncWithQuickbooks = 1,
            deptData.quickbookId = serviceUniqueId
        }
      } catch (qbError) {
        console.error("QuickBooks Integration Error: ", qbError);
        quickbookError = qbError
      }

      ///////////////////////////////////
      let modelStatus = await Product.addLabourCostServices(deptData);
      if (modelStatus) {
        // Notification
        let notificationMsg = `New service "${deptData['name']}" has been added.`;
        let notificationUrl = "servicecost";
        // Alert to user of updated service
        const incrementResult_User = await Admin.incrementField(deptData['added_by'], "noti_count", 1);
        let notificationData_user = {
          user_id: deptData['added_by'],
          url: notificationUrl,
          timestamp: currentDate,
          message: notificationMsg,
          status: 1,
          from_id: deptData['added_by'],
        };
        const addNotificationResult_user = await Admin.addNotification(notificationData_user);
        // Alert to admins of updated service
        let adminIds = await Admin.getAllAdminIds();
        let notificationData = {};
        for (const adminId of adminIds) {
          if (adminId != deptData['added_by']) {
            const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
            notificationData = {
              user_id: adminId,
              url: notificationUrl,
              timestamp: currentDate,
              message: notificationMsg,
              status: 1,
              from_id: deptData['added_by'],
            };
            const addNotificationResult = await Admin.addNotification(notificationData);
          }
        }

        res.send({ status: 1,service_id:modelStatus, message: `Service "${deptData['name']}" added successfully`, qb: quickbookError });
      } else {
        res.send({ status: 0, message: "Failed to add service" });
      }
    } else {
      res.send({ status: 0, message: "Name already exists" });
    }
  } catch (error) {
    console.log('Error : ', error);
  }
}

const updateLabourCostServices = async (req, res) => {
  try {
    serviceUniqueId = null;
    let quickbookError = null;
    let currentDate = Math.round(moment().valueOf() / 1000);
    let { id } = req.body;
    const updated_by = req.body.userId;

    // Normalize incoming fields
    let deptData = {
      name: req.body.name,
      description: req.body.description,
      markup: req.body.markup,
      service_type: req.body.type.id,
      total_cost: Number(req.body.total_cost) || 0,
      cost: Number(req.body.cost) || 0,
      status: req.body.status,
      workflow: req.body.workflow,
      inhouse: Number(req.body.inHouse ?? 0),
      avatarUrl: req.body.avatarUrl,
      makeAsTask: req.body.makeAsTask ?? 1,
      makeInstallCard: req.body.makeInstallCard ?? 1,
      jobCost: req.body.jobCost ?? 1,
      taxable: Number(req.body.taxable) || 0,
      taxCodeId: req.body.taxCodeId || null,
      taxCode: req.body.taxCode || null,
      qb_income_account: req.body?.qb_income_account?.glCode || null,
      qb_expense_account: req.body?.qb_expense_account?.glCode || null,
    };

    // Name validations
    let productNameExist = await Product.isProductNameExist(deptData.name);
    let isServiceNameExist = await Product.isServiceNameExist(deptData.name, id);

    if (productNameExist === true || isServiceNameExist === true) {
      return res.send({ status: 0, message: "Name already exists" });
    }

    // Previous service data (for QB update etc.)
    let oldProductData = await Product.getLabourCostServicesById(id);
    if (!oldProductData || !oldProductData.length) {
      return res.send({ status: 0, message: "Service not found" });
    }

    const originInhouse = oldProductData[0]?.originInhouse ?? oldProductData[0]?.inhouse ?? null;
    deptData.originInhouse = originInhouse;

    const serviceData = {
      existingName: oldProductData[0].name,
      name: deptData.name,
      total_cost: deptData.total_cost,
      cost: deptData.cost,
      qb_income_account: deptData.qb_income_account,
      qb_expense_account: deptData.qb_expense_account,
      quickbookId: oldProductData[0]?.quickbookId,
    };

    // ==============================
    // QuickBooks Sync (unchanged)
    // ==============================
    try {
      if (oldProductData[0].syncWithQuickbooks === 0) {
        const item = await findItemByNameWithLocationCode(deptData?.name);
        console.log("Checking item exist in quickbooks...............",item)
        if(item){
          deptData.quickbookId = item,
          deptData.syncWithQuickbooks=1
        }else{
          console.log("continue adding service in quickbooks...............")
        await addServiceToQuickBooks(deptData);
        if (serviceUniqueId) {
          deptData.syncWithQuickbooks = 1;
          deptData.quickbookId = serviceUniqueId;
        }
        }
      } else {
        const catalogValidationResult = await validateInventoryItemsInQbForPo(serviceData,"quickbookId");
        console.log("service validation result in quickbooks...............",catalogValidationResult)
       if(catalogValidationResult.itemData){
        deptData.syncWithQuickbooks=0,
        deptData.quickbookId=null
       }else{
        console.log("continue updating service in quickbooks...............")
        await updateServiceInQuickBooks(serviceData);
       }
        if (serviceUniqueId) {
          deptData.syncWithQuickbooks = 1;
          deptData.quickbookId = serviceUniqueId;
        }
      }
    } catch (qbError) {
      console.error("QuickBooks Integration Error: ", qbError);
      quickbookError = qbError;
      // do not block update on QB failure
    }

    // ==============================
    // Persist the service itself
    // ==============================
    const modelStatus = await Product.updateLabourCostServices(id, deptData);
    if (!modelStatus) {
      return res.send({ status: 0, message: "Failed to update service" });
    }

    // ===========================================================
    // Recalculate ALL recipes that reference this service (NEW)
    // EXACT SAME CALCULATION BLOCK USED IN deleteFinishedProduct
    // ===========================================================
    // try {
    //   const allRecipies = await Product.fetchRecipesBasedOnService(id);

    //   for (let j = 0; j < allRecipies.length; j++) {
    //     // Use the same fetch as in updateProduct to get highest_unit_cost/markup/services
    //     const getRecipe = await Recipe.getRecipeByIdForAutoUpdate(allRecipies[j].recipe_id);
    //     const recipeRow = getRecipe?.[0];
    //     if (!recipeRow) continue;

    //     // ---------- MATERIAL SIDE (same as deleteFinishedProduct) ----------
    //     // Base expected material cost using highest_unit_cost * quantity
    //     const expectedMaterialCost =
    //       recipeRow?.ingredients?.reduce(
    //         (acc, cur) => acc + (Number(cur?.highest_unit_cost || 0) * Number(cur?.quantity || 0)),
    //         0
    //       ) || 0;

    //     // Compute with highest_unit_cost & markup
    //     const totals = (recipeRow?.ingredients || []).reduce(
    //       (acc, ing) => {
    //         const qty = Number(ing?.quantity ?? 0);
    //         const unitCost = Number(ing?.highest_unit_cost ?? ing?.materialCost ?? 0);
    //         const markupPct = Number(ing?.markup ?? 0);

    //         const lineSub = unitCost * qty;                // Sub Total
    //         const lineRetail = (lineSub * markupPct) / 100; // Retail Amount
    //         acc.subTotal += lineSub;
    //         acc.retail += lineRetail;
    //         acc.total += lineSub + lineRetail;             // Total = Sub + Retail
    //         return acc;
    //       },
    //       { subTotal: 0, retail: 0, total: 0 }
    //     );

    //     const expectedMaterialCostWithMarkup = Number(totals.total.toFixed(2));

    //     // ---------- SERVICE SIDE (same approach as deleteFinishedProduct) ----------
    //     // total after any service-level markup/amounts
    //     const expectedTotalServiceCost = (recipeRow?.services || []).reduce((sum, s) => {
    //       const amt = Number(s?.serviceAmount) || 0;
    //       const qty = Number(s?.quantity) || 0;
    //       return sum + (amt * qty);
    //     }, 0);

    //     // raw service cost (no markup)
    //     const expectedServiceCost = (recipeRow?.services || []).reduce((sum, s) => {
    //       const val = Number(s?.serviceValue) || 0;
    //       const qty = Number(s?.quantity) || 0;
    //       return sum + (val * qty);
    //     }, 0);

    //     // Keep original expected_cost parity (materials raw + services raw)
    //     const expectedCost = Number(expectedMaterialCost) + Number(expectedServiceCost);

    //     // ---------- UPDATE RECIPE ----------
    //     await Recipe.updateRecipe(
    //       {
    //         expected_cost: expectedCost,
    //         expected_material_cost: expectedMaterialCost,
    //         expected_material_cost_with_markup: expectedMaterialCostWithMarkup,
    //         expected_total_service_cost: expectedTotalServiceCost,
    //         expected_service_cost: expectedServiceCost,
    //         expected_service_cost_with_markup: expectedTotalServiceCost, //put everywhere
    //         expected_cost_with_markup:
    //           Number(expectedMaterialCostWithMarkup) + Number(expectedTotalServiceCost),
    //       },
    //       recipeRow.id
    //     );

    //     // ---------- UPDATE PRODUCTS tied to this recipe (same as deleteFinishedProduct) ----------
    //     const allProductsWithCategory = await Product.fetchAllProductsByRecipe(true, false, recipeRow.id);
    //     for (let k = 0; k < allProductsWithCategory.length; k++) {
    //       const p = allProductsWithCategory[k];
    //       const emc = Number(p.expected_material_cost || 0);
    //       const ets = Number(p.expected_total_service_cost || 0);
    //       const cost = emc + ets;

    //       const productUpdate = {
    //         price: cost + (emc * (Number(p.wholesaleMarkup || 0)) / 100),
    //         // retail_price: cost + (emc * (Number(p.retailMarkup || 0)) / 100),
    //         special_price: emc,
    //         formula_cost: expectedMaterialCostWithMarkup + expectedTotalServiceCost,
    //       };

    //       await Product.updateProduct(p.id, productUpdate);
    //     }

    try {
      const allRecipies = await Product.fetchRecipesBasedOnService(id);

      for (let j = 0; j < allRecipies.length; j++) {
        const getRecipe = await Recipe.getRecipeByIdForAutoUpdate(allRecipies[j].recipe_id);
        const recipeRow = getRecipe?.[0];
        if (!recipeRow) continue;

        // ---- Keep existing MATERIAL numbers as-is (do not recompute / overwrite)
        const emc = Number(recipeRow?.expected_material_cost || 0);
        const emcWithMarkup = Number(recipeRow?.expected_material_cost_with_markup || 0);

        // ---- Recompute SERVICE side only
        const expectedTotalServiceCost = (recipeRow?.services || []).reduce((sum, s) => {
          const amt = Number(s?.serviceAmount) || 0;  // service amount after markup logic on each line
          const qty = Number(s?.quantity) || 0;
          return sum + (amt * qty);
        }, 0);

        const expectedServiceCost = (recipeRow?.services || []).reduce((sum, s) => {
          const val = Number(s?.serviceValue) || 0;   // raw service value (no markup)
          const qty = Number(s?.quantity) || 0;
          return sum + (val * qty);
        }, 0);

        const expectedTotalInstallLaborCost = (recipeRow?.services || []).reduce((sum, s) => {
          if (s.service_id !== 10) return sum;  // ✅ skip all services except id == 10
          const amt = Number(s?.serviceAmount) || 0;  // service amount after markup
          const qty = Number(s?.quantity) || 0;
          return sum + (amt * qty);
        }, 0);

        const expectedInstallLaborCost = (recipeRow?.services || []).reduce((sum, s) => {
          if (s.service_id !== 10) return sum;  // ✅ skip all services except id == 10
          const val = Number(s?.serviceValue) || 0;   // raw service value (no markup)
          const qty = Number(s?.quantity) || 0;
          return sum + (val * qty);
        }, 0);
        // ---- Combined totals WITHOUT changing material fields
        const expectedCost = emc + expectedServiceCost;                              // raw materials + raw services
        const expectedCostWithMarkup = emcWithMarkup + expectedTotalServiceCost;     // materials(with markup) + services(total)

        // ---- Update RECIPE: only service-related + combined totals
        await Recipe.updateRecipe(
          {
            // DO NOT set expected_material_cost or expected_material_cost_with_markup here
            expected_service_cost: expectedServiceCost,
            expected_total_service_cost: expectedTotalServiceCost,
            expected_service_cost_with_markup: expectedTotalServiceCost, // parity with your scheme
            expected_cost: expectedCost,
            expected_cost_with_markup: expectedCostWithMarkup,
            expected_install_labor_cost: parseFloat(Number(expectedInstallLaborCost)).toFixed(2),
            expected_install_labor_cost_with_markup: parseFloat(Number(expectedTotalInstallLaborCost)).toFixed(2)
          },
          recipeRow.id
        );

        // ---- Update PRODUCTS tied to this recipe using updated SERVICE totals
        const allProductsWithCategory = await Product.fetchAllProductsByRecipe(true, false, recipeRow.id);
        for (const p of allProductsWithCategory) {
          const wholesaleMarkup = Number(p.wholesaleMarkup || 0);

          // Base 'cost' follows your convention:
          //   cost = expected_material_cost (raw) + expected_total_service_cost (with markup)
          const cost = emc + expectedTotalServiceCost;

          const productUpdate = {
            price: cost + (emc * wholesaleMarkup / 100),        // keep same pricing logic
            special_price: emc,                                  // store raw material side here (unchanged)
            formula_cost: emcWithMarkup + expectedTotalServiceCost // catalog-style total for formulas
          };

          await Product.updateProduct(p.id, productUpdate);
        }

        // // ---------- Refresh & push EMC-with-markup into catalog (same as deleteFinishedProduct) ----------
        // const refreshedRecipe = await Recipe.getRecipeById(recipeRow.id);
        // const emcWithMarkup = Number(refreshedRecipe?.[0]?.expected_material_cost_with_markup) || 0;
        // console.log('emcWithMarkup**********', emcWithMarkup)

        // const catalogProducts = await Product.catalogByReceipte(recipeRow.id);
        // for (const cat of catalogProducts) {
        //   const prevSpecial = Number(cat.special_price || 0);
        //   console.log('prevSpecial**********', prevSpecial)
        //   const prevFormula = Number(cat.formula_cost || 0);
        //   console.log('prevFormula**********', prevFormula)
        //   const service_cost = prevFormula - prevSpecial; // preserve service delta
        //   console.log('service_cost**********', service_cost)

        //   const newSpecial = emcWithMarkup;
        //   console.log('newSpecial**********', newSpecial)
        //   const newFormula = service_cost + newSpecial;
        //   console.log('newFormula**********', newFormula)

        //   await Product.updateProduct(cat.id, {
        //     special_price: newSpecial,
        //     formula_cost: newFormula,
        //   }); 
        // } // may be it will be need in the futu
      }
    } catch (recalcErr) {
      console.error("Error recalculating recipes after service update:", recalcErr);
      // continue — don't fail the request because of recalculation error
    }

    // ==============================
    // Notifications (unchanged)
    // ==============================
    const notificationMsg = `The details of service "${deptData.name}" have been updated.`;
    const notificationUrl = "servicecost";

    // Alert to user of updated service
    await Admin.incrementField(updated_by, "noti_count", 1);
    await Admin.addNotification({
      user_id: updated_by,
      url: notificationUrl,
      timestamp: currentDate,
      message: notificationMsg,
      status: 1,
      from_id: updated_by,
    });

    // Alert to admins
    const adminIds = await Admin.getAllAdminIds();
    for (const adminId of adminIds) {
      if (adminId !== updated_by) {
        await Admin.incrementField(adminId, "noti_count", 1);
        await Admin.addNotification({
          user_id: adminId,
          url: notificationUrl,
          timestamp: currentDate,
          message: notificationMsg,
          status: 1,
          from_id: updated_by,
        });
      }
    }

    return res.send({
      status: 1,
      service_id: id,
      message: `Service "${deptData.name}" updated successfully`,
      qb: quickbookError,
    });
  } catch (error) {
    console.error("Error: ", error);
    return res.send({
      status: 0,
      message: "An error occurred while updating the service.",
    });
  }
};

// const updateLabourCostServices = async (req, res) => {
//   try {
//     let currentDate = Math.round(moment().valueOf() / 1000);
//     let { id } = req.body;
//     const updated_by = req.body.userId;
//     let deptData = {};
//     deptData['name'] = req.body.name;
//     deptData['markup'] = req.body.markup;
//     deptData['service_type'] = req.body.type.id;
//     deptData['total_cost'] = req.body.inHouse ? req.body.total_cost : 0;
//     deptData['cost'] = req.body.inHouse ? req.body.cost : 0;
//     deptData['status'] = req.body.status;
//     deptData['inhouse']=req.body.inHouse ? 1 : 0;
//     let modelStatus = await Product.updateLabourCostServices(id, deptData);
//     if (modelStatus) {
//       let allRecipies=await Product.fetchRecipesBasedOnService(id)
//       for(let i=0;i<allRecipies.length;i++){
//         let getRecipe=await Recipe.getRecipeById(allRecipies[i].recipe_id)

//         let expectedTotalServiceCost=getRecipe[0]?.services?.reduce((accumulator, currentValue) => {
//           return accumulator + (currentValue?.serviceAmount* currentValue?.quantity);
//         }, 0) || 0
//         let expectedServiceCost=getRecipe[0]?.services?.reduce((accumulator, currentValue) => {
//           return accumulator + (currentValue?.serviceValue* currentValue?.quantity);
//         }, 0) || 0

//         let expectedCost = getRecipe[0].expected_material_cost + expectedServiceCost
//         let data={
//           expected_cost:expectedCost,
//           expected_service_cost:expectedServiceCost,
//           expected_total_service_cost:expectedTotalServiceCost,
//         }
//         let recipeId=await Recipe.updateRecipe(data,getRecipe[0].id)
//         let allProductsWithCategory=await Product.fetchAllProductsByRecipe(true,false,getRecipe[0].id)
//         console.log("#################ProductCategory",allProductsWithCategory); 
//         for(let i=0;i<allProductsWithCategory.length;i++){
//           let cost=allProductsWithCategory[i].expected_material_cost+allProductsWithCategory[i].expected_total_service_cost
//           let product={}
//           product.price=cost+(allProductsWithCategory[i].expected_material_cost* allProductsWithCategory[i].wholesaleMarkup/100)
//           product.retail_price=cost+(allProductsWithCategory[i].expected_material_cost*allProductsWithCategory[i].retailMarkup/100)
//           product.special_price=allProductsWithCategory[i].expected_material_cost
//           let updated = await Product.updateProduct(allProductsWithCategory[i].id, product);
//         }
//         console.log("#########expectedTotalServiceCost",expectedTotalServiceCost);
//       }
//       console.log("######alllRecipies",allRecipies);
//       // Notification
//       let notificationMsg = `The details of service "${deptData['name']}" has been updated.`;
//       let notificationUrl = "servicecost";
//       // Alert to user of updated service
//       const incrementResult_User = await Admin.incrementField(updated_by, "noti_count", 1);
//       let notificationData_user = {
//         user_id: updated_by,
//         url: notificationUrl,
//         timestamp: currentDate,
//         message: notificationMsg,
//         status: 1,
//         from_id: updated_by,
//       };
//       const addNotificationResult_user = await Admin.addNotification(notificationData_user);
//       // Alert to admins of updated service
//       let adminIds = await Admin.getAllAdminIds();
//       let notificationData = {};
//       for (const adminId of adminIds) {
//         if (adminId != updated_by) {
//           const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
//           notificationData = {
//             user_id: adminId,
//             url: notificationUrl,
//             timestamp: currentDate,
//             message: notificationMsg,
//             status: 1,
//             from_id: updated_by,
//           };
//           const addNotificationResult = await Admin.addNotification(notificationData);
//         }
//       }
//       res.send({ status: 1, message: `Service "${deptData['name']}" updated successfully` });
//     } else {
//       res.send({ status: 0, message: "Failed to update service" });
//     }
//   } catch (error) {
//     console.log('Error : ', error);
//   }
// }



const deleteLabourCostServices = async (req, res) => {
  try {
    let currentDate = Math.round(moment().valueOf() / 1000);
    let { id, name } = req.body;
    const updated_by = req.body.userId;

    let productData = await Product.getLabourCostServicesById(id);


    /////////////////qb
    try {
      console.log('statussssssssssssssssss', req.body.status);

      if (productData[0]?.quickbookId) {
        await deleteProductFromQuickBooks(productData[0]?.quickbookId, req.body.status);
      }
    } catch (quickBooksError) {
      console.error("Error deleting product from QuickBooks:", quickBooksError);
    }
    ////////////////


    let data = {};
    data['status'] = req.body.status;
    let modelStatus = await Product.updateLabourCostServices(id, data);
    if (modelStatus) {
      // Notification
      let notificationMsg;
      if (data['status'] === 2) {
        notificationMsg = `The service "${productData[0]?.name}" has been deleted.`;
      } else if (data['status'] === 0) {
        notificationMsg = `The service "${productData[0]?.name}" has been made inactive.`;
      } else {
        notificationMsg = `The service "${productData[0]?.name}" has been made active.`;
      }
      let notificationUrl = "servicecost";
      // Alert to user of updated service
      const incrementResult_User = await Admin.incrementField(updated_by, "noti_count", 1);
      let notificationData_user = {
        user_id: updated_by,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: updated_by,
      };
      const addNotificationResult_user = await Admin.addNotification(notificationData_user);
      // Alert to admins of updated service
      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != updated_by) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: updated_by,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
      res.send({ status: 1, message: notificationMsg, data: modelStatus });
    } else {
      res.send({ status: 0, message: "Failed to update service" });
    }
  } catch (error) {
    console.log('Error : ', error);
    res.send({ status: 500, message: "Error updating service", error });
  }
}

const moveLabourCostService = async (req, res) => {
  try {
    const { id, ids, targetInhouse } = req.body;

    // Check if either id or ids is provided
    if ((id === null || id === undefined) && (!Array.isArray(ids) || ids.length === 0)) {
      return res.send({ status: 0, message: "Service id(s) is required" });
    }

    if (targetInhouse === undefined || targetInhouse === null) {
      return res.send({ status: 0, message: "Target inhouse value is required" });
    }

    // Validate targetInhouse is a valid number (0, 1, 2, or 3)
    const validTargetValues = [0, 1, 2, 3];
    if (!validTargetValues.includes(Number(targetInhouse))) {
      return res.send({ status: 0, message: "Invalid target inhouse value" });
    }

    // Determine if this is a single or bulk operation
    const serviceIds = ids || [id];
    const results = [];
    const updatedIds = [];
    let successCount = 0;
    let failCount = 0;

    for (const serviceId of serviceIds) {
      try {
        const serviceData = await Product.getLabourCostServicesById(serviceId);
        if (!serviceData || !serviceData.length) {
          results.push({ id: serviceId, success: false, message: "Service not found" });
          failCount++;
          continue;
        }

        const currentService = serviceData[0];
        if (Number(currentService.status) === 2) {
          results.push({ id: serviceId, success: false, message: "Cannot move a deleted service" });
          failCount++;
          continue;
        }

        const payload = {
          inhouse: Number(targetInhouse),
          originInhouse: currentService.originInhouse ?? currentService.inhouse,
          isNew: 1
        };

        await Product.updateLabourCostServices(serviceId, payload);
        results.push({ id: serviceId, success: true, message: "Service moved successfully", ...payload });
        updatedIds.push(serviceId);
        successCount++;
      } catch (serviceError) {
        console.error(`Error moving service ${serviceId}:`, serviceError);
        results.push({ id: serviceId, success: false, message: "Failed to move service" });
        failCount++;
      }
    }

    // Return appropriate response based on operation type and results
    if (serviceIds.length === 1) {
      // Single operation - return the result of the single service
      const result = results[0];
      return res.send({
        status: result.success ? 1 : 0,
        message: result.message,
        result: result.success ? { id: result.id, updatedIds, ...result } : null
      });
    } else {
      // Bulk operation - return summary
      return res.send({
        status: successCount > 0 ? 1 : 0,
        message: `${successCount} service${successCount !== 1 ? 's' : ''} moved successfully${failCount > 0 ? `, ${failCount} failed` : ''}`,
        results: results,
        result: { successCount, failCount, total: serviceIds.length, updatedIds }
      });
    }
  } catch (error) {
    console.error("Error moving labour cost service:", error);
    return res.send({ status: 0, message: "Failed to move service(s)" });
  }
};

const getAllLabourCostServices = async (req, res) => {
  try {
    const result = await Product.getAllLabourCostServices();
    const serviceTypes = await Product.getAllLabourCostServiceTypes();
    if (result || serviceTypes) {
      res.send({ status: 1, message: 'Data fetched successfully', results: result, serviceTypes: serviceTypes })
    } else {
      res.send({ status: 0, message: 'No Labour cost data found' })
    }
  } catch (error) {
    console.log('Error : ', error);
    res.send({ status: 500, message: 'Server error', error: error })
  }
}

const getAllLabourCostServicesOnly = async (req, res) => {
  try {
    let labourCostServices = await Product.getAllLabourCostServices();
    res.send({ status: 1, labourCostServices, message: "Labour cost services fetched successfully" });
  } catch (error) {
    console.log(error, 'error in get labour cost services only')
    res.send({ status: 0, message: 'failed to get request labour cost services only' })
  }
}

const getAllLabourCostServicesForJobCost = async (req, res) => {
  try {
    const result = await Product.getAllLabourCostServicesForJobCost();
    const serviceTypes = await Product.getAllLabourCostServiceTypes();
    if (result || serviceTypes) {
      res.send({ status: 1, message: 'Data fetched successfully', results: result, serviceTypes: serviceTypes })
    } else {
      res.send({ status: 0, message: 'No Labour cost data found' })
    }
  } catch (error) {
    console.log('Error : ', error);
    res.send({ status: 500, message: 'Server error', error: error })
  }
}

const getLabourCostServicesById = async (req, res) => {
  try {
    const { editId } = req.body
    const result = await Product.getLabourCostServicesById(editId);
    if (result) {
      res.send({ status: 1, message: 'Data fetched successfully', result: result })
    } else {
      res.send({ status: 0, message: 'No Labour cost data found' })
    }
  } catch (error) {
    console.log('Error : ', error);
    res.send({ status: 500, message: 'Server error', error: error })
  }
}

const addLabourCostServiceType = async (req, res) => {
  try {
    let currentDate = Math.round(moment().valueOf() / 1000);
    let deptData = {};
    deptData['name'] = req.body.name;
    deptData['added_by'] = req.body.userId;
    deptData['timestamp'] = currentDate;
    deptData['status'] = 1;


    let modelStatus = await Product.addLabourCostServiceType(deptData);
    if (modelStatus) {
      // Notification
      let notificationMsg = `New service Type "${deptData['name']}" has been added.`;
      let notificationUrl = "servicecost";
      // Alert to user of updated service
      const incrementResult_User = await Admin.incrementField(deptData['added_by'], "noti_count", 1);
      let notificationData_user = {
        user_id: deptData['added_by'],
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: deptData['added_by'],
      };
      const addNotificationResult_user = await Admin.addNotification(notificationData_user);
      // Alert to admins of updated service
      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != deptData['added_by']) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: deptData['added_by'],
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }

      res.send({ status: 1, message: `Service Type "${deptData['name']}" added successfully` });
    } else {
      res.send({ status: 0, message: "Failed to add service type" });
    }
  } catch (error) {
    console.log('Error : ', error);
  }
}

const updateLabourCostServiceTypes = async (req, res) => {
  try {
    let currentDate = Math.round(moment().valueOf() / 1000);
    let { id } = req.body;
    const updated_by = req.body.userId;
    let deptData = {};
    deptData['name'] = req.body.name;
    deptData['status'] = req.body.status;
    let modelStatus = await Product.updateLabourCostServiceType(id, deptData);
    if (modelStatus) {
      // Notification
      let notificationMsg = `The service type "${deptData['name']}" has been updated.`;
      let notificationUrl = "servicecost";
      // Alert to user of updated service
      const incrementResult_User = await Admin.incrementField(updated_by, "noti_count", 1);
      let notificationData_user = {
        user_id: updated_by,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: updated_by,
      };
      const addNotificationResult_user = await Admin.addNotification(notificationData_user);
      // Alert to admins of updated service
      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != updated_by) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: updated_by,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
      res.send({ status: 1, message: `Service type "${deptData['name']}" updated successfully` });
    } else {
      res.send({ status: 0, message: "Failed to update service type" });
    }
  } catch (error) {
    console.log('Error : ', error);
  }
}

const deleteLabourCostServiceTypes = async (req, res) => {
  try {
    let currentDate = Math.round(moment().valueOf() / 1000);
    let { id, name } = req.body;
    const updated_by = req.body.userId;
    let data = {};
    data['status'] = req.body.status;
    let modelStatus = await Product.updateLabourCostServiceType(id, data);
    if (modelStatus) {
      // Notification
      let notificationMsg;
      if (data['status'] === 2) {
        notificationMsg = `The service type "${name}" has been deleted.`;
      } else if (data['status'] === 0) {
        notificationMsg = `The service type "${name}" has been made inactive.`;
      } else {
        notificationMsg = `The service type "${name}" has been made active.`;
      }
      let notificationUrl = "servicecost";
      // Alert to user of updated service
      const incrementResult_User = await Admin.incrementField(updated_by, "noti_count", 1);
      let notificationData_user = {
        user_id: updated_by,
        url: notificationUrl,
        timestamp: currentDate,
        message: notificationMsg,
        status: 1,
        from_id: updated_by,
      };
      const addNotificationResult_user = await Admin.addNotification(notificationData_user);
      // Alert to admins of updated service
      let adminIds = await Admin.getAllAdminIds();
      let notificationData = {};
      for (const adminId of adminIds) {
        if (adminId != updated_by) {
          const incrementResult = await Admin.incrementField(adminId, "noti_count", 1);
          notificationData = {
            user_id: adminId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: updated_by,
          };
          const addNotificationResult = await Admin.addNotification(notificationData);
        }
      }
      res.send({ status: 1, message: notificationMsg });
    } else {
      res.send({ status: 0, message: "Failed to update service type" });
    }
  } catch (error) {
    console.log('Error : ', error);
  }
}

const getAllLabourCostServiceTypes = async (req, res) => {
  try {
    const result = await Product.getAllLabourCostServiceTypes();
    if (result) {
      for (const row of result) {
        const types = await Product.getLabourCostServiceTypesById(row.id)
        row.services = types
      }
      res.send({ status: 1, message: 'Data fetched successfully', results: result })
    } else {
      res.send({ status: 0, message: 'No Labour cost data found' })
    }
  } catch (error) {
    console.log('Error : ', error);
    res.send({ status: 500, message: 'Server error', error: error })
  }
}

const getLabourCostServiceTypesById = async (req, res) => {
  try {
    const { editId } = req.body
    const result = await Product.getLabourCostServiceTypesById(editId);
    if (result) {
      res.send({ status: 1, message: 'Data fetched successfully', result: result })
    } else {
      res.send({ status: 0, message: 'No Labour cost type found' })
    }
  } catch (error) {
    console.log('Error : ', error);
    res.send({ status: 500, message: 'Server error', error: error })
  }
}

const addDataToProductTableOnReq = async (req, res) => {
  try {
    const { values } = req.body
    let currentDate = Math.round(moment().valueOf() / 1000);
    let result = '';
    for (const product of values.products) {
      quickbookId = null;
      const productData = {
        inhouse: 2,
        price: Number(product.retail_price),
        retail_price: Number(product.retail_price),
        added_by: 1,
        name: product.name || product.product_name,
        timestamp: currentDate,
      };

      const sampleProductDataForQuickbook = {
        name: product.name || product.product_name,
        unit_qty: product.quantity || 0,
        cost: Number(product.retail_price) || 0,
        wholeSalePrice: Number(product.retail_price) || 0,
        retailPrice: Number(product.retail_price) || 0,
      };

      let name = product.name ? product.name : product.product_name
      let productNameExist = await Product.findProductByName(name);
      let isServiceNameExist = await Product.isServiceNameExist(name);

      if (productNameExist || isServiceNameExist) {
        console.log(productNameExist.dataValues, "Product exists");
        const existingProductId = productNameExist.dataValues.id;
        if (productNameExist?.dataValues?.syncWithQuickbooks === 0) {
          // Add to QuickBooks if not synced
          try {
            const productResponse = await addProductToQuickBooks(sampleProductDataForQuickbook);

            console.log('productResponseproductResponse', productResponse)
            if (quickbookId) {
              productData.syncWithQuickbooks = 1;
              productData.quickbookId = quickbookId;
            }
          } catch (error) {
            console.log('Error adding to QuickBooks', error);
          }
        }
      } else {
        // Add to QuickBooks if product doesn't exist in your system
        try {
          const productResponse = await addProductToQuickBooks(sampleProductDataForQuickbook);
          if (productResponse) {
            productData.syncWithQuickbooks = 1;
            productData.quickbookId = productResponse.quickbookId;
          }
        } catch (error) {
          console.log('Error adding to QuickBooks', error);
        }

        // Add new product to your system
        try {
          result = await Product.addProduct(productData);
          console.log('Product added', result);
        } catch (error) {
          console.log('Error adding product in your system', error);
        }
      }
    }
    return res.send({ status: 1, message: "product added successfully", result: result })
  } catch (error) {
    console.log('error==addDataToProductTableOnReq=', error)
    return res.send({ status: 0, err: error, message: "server error" })
  }
};




const fetchQBCategories = async (req, res) => {
  let token = await getNewAccessToken(); // Ensure valid token
  console.log("🔹 Access Token:", token);

  if (!token) {
    console.error("❌ Access Token is missing or expired!");
    return;
  }

  // Use Sandbox or Production API based on the environment
  const baseURL = PRODUCTION
    ? `https://quickbooks.api.intuit.com`
    : `https://sandbox-quickbooks.api.intuit.com`;

  const url = `${baseURL}/v3/company/${realmId}/query?query=SELECT * FROM Item WHERE Type = 'Category'&minorversion=4`;

  try {
    const response = await axios.get(url, {
      headers: {
        Authorization: `Bearer ${token}`,
        Accept: "application/json",
        "Content-Type": "application/json",
      },
    });

    const allItems = response.data.QueryResponse?.Item || [];

    if (allItems.length === 0) {
      console.log("⚠️ No categories found in QuickBooks.");
      return [];
    }

    // console.log("✅ Fetched Categories:", JSON.stringify(allItems, null, 2));
    return res.send({ result: allItems, status: 1 })
  } catch (error) {
    console.error(
      "❌ Error fetching categories:",
      JSON.stringify(error.response?.data || error.message, null, 2)
    );
    return res.send({ error: error, status: 0 })
  }
};

const moveMaterialToClientOwned = async (request, reply) => {
  try {
    let { userId, id } = request.body;
    let currentDate = Math.round(moment().valueOf() / 1000);
    let products = await Product.getProductById(id);
    let productData = products[0]
    delete productData.id;
    productData.name = productData.name + '-COM'
    productData.inhouse = 0
    productData.added_by = userId
    productData.timestamp = currentDate
    productData.isNew = 1

    const materialInventoryData = await Product.getMaterialInventoryByMaterialId(id)

    let materialResult = 0;
    if (materialInventoryData) {
      materialResult = sumQuantities(materialInventoryData);
    }
    console.log('dataaaaaaaaaaaaaaaaaaaaaaaa', materialResult);

    const sampleProductData = {
      name: productData.name,
      description: productData.description,
      price: productData.price ? productData.price : 0,
      unit_qty: materialResult || 0,
      qb_category: productData.qb_category,
      qb_sub_category: productData.qb_sub_category,
      incomeGlCode: productData.qb_income_account,
      expenseGlCode: productData.qb_expense_account,
    };

    try {
      await addMaterialToQuickBooks(sampleProductData)
    } catch (error) {
      console.log(error, "error")
    }

    if (materialQbId) {
      productData.syncWithQuickbooks = 1,
        productData.quickbookId = materialQbId
    } else {
      productData.syncWithQuickbooks = 0
      productData.quickbookId = null
    }

    let productNameExist = await Product.isMaterialNameExist(productData.name);
    let isServiceNameExist = await Product.isServiceNameExist(productData.name);
    // console.log("productNameExist : ", productNameExist);
    if (productNameExist != true && isServiceNameExist != true) {
      try {
        let result = await Product.addProduct(productData);
        if (result) {
          const updateData = {
            material_id: result?.dataValues?.id
          }
          const updateMaterial = await Product.updateMaterialInventory(id, updateData);
          // Notification
          let notificationMsg = "Material has been moved to Client Owned!"
          let notificationUrl = "products";

          // Alert to user of new added product

          const incrementResult_User = await Admin.incrementField(
            productData.added_by,
            "noti_count",
            1
          );
          let notificationData_user = {
            user_id: userId,
            url: notificationUrl,
            timestamp: currentDate,
            message: notificationMsg,
            status: 1,
            from_id: userId,
          };
          const addNotificationResult_user = await Admin.addNotification(
            notificationData_user
          );
          // Alert to all admins of new added product

          let adminIds = await Admin.getAllAdminIds();
          let notificationData = {};
          for (const adminId of adminIds) {
            if (adminId != productData.added_by) {
              const incrementResult = await Admin.incrementField(
                adminId,
                "noti_count",
                1
              );
              notificationData = {
                user_id: adminId,
                url: notificationUrl,
                timestamp: currentDate,
                message: notificationMsg,
                status: 1,
                from_id: userId,
              };
              const addNotificationResult = await Admin.addNotification(
                notificationData
              );
            }
          }
          reply.send({
            status: 1,
            message: notificationMsg,
          });
        } else {
          console.log("Failed to move material to client owned");
          reply.send({ status: 0, message: "Failed to move material to client owned" });
        }
      } catch (error) {
        console.log("Failed to move material to client owned : ", error);
        reply.send({ status: 0, message: "Failed to move material to client owned" });
      }
    } else {
      console.log("The material already exist!");
      reply.send({ status: 2, message: "The material already exist!" });
    }
  } catch (error) {
    console.log("Server error", error);
    reply.status(500).send({ message: "Server error" });
  }
};

///////////////////qb accounts

async function getGLCodes() {
  try {
    let token = await getNewAccessToken(); // Ensure you have a valid token

    const url = PRODUCTION
      ? `https://quickbooks.api.intuit.com/v3/company/${realmId}/query?query=SELECT * FROM Account &minorversion=65`
      : `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/query?query=SELECT * FROM Account &minorversion=65`;

    const headers = {
      "Authorization": `Bearer ${token}`,
      "Accept": "application/json",
      "Content-Type": "application/json"
    };

    const response = await axios.get(url, { headers });

    if (!response.data || !response.data.QueryResponse) {
      console.error("QuickBooks API Response Missing Data:", response.data);
      return [];
    }

    const accounts = response.data.QueryResponse.Account || [];

    // Filter only accounts that have a valid AcctNum (GL Code)
    const filteredAccounts = accounts
      .filter(account => account.AcctNum && account.AcctNum.trim() !== "")
      .map(account => ({
        id: account.Id,
        name: account.Name,
        glCode: account.AcctNum || null, // Ensure null if AcctNum is missing
        type: account.AccountType
      }));

    console.log("Filtered Accounts:", filteredAccounts.length);

    return filteredAccounts;
  } catch (error) {
    console.error("Error fetching GL Codes:", error.response?.data || error.message);
    return [];
  }
}


//////////////////////////

const fetchQbAccount = async (req, res) => {
  try {

    const [incomeAccounts, expenseAccounts, cogsAccounts] = await Promise.all([
      getAllAccountsGLCodes('Income'),  // getAllAccountsGLCodes('Income', 'SalesOfProductIncome'),
      getAllAccountsGLCodes('Expense'),
      getAllAccountsGLCodes('Cost of Goods Sold')
    ]);

    const result = [...incomeAccounts, ...expenseAccounts, ...cogsAccounts]

    // const result = await getAllGLCodes()

    if (result) {
      res.send({ status: 1, result })
    } else {
      res.send({ status: 0 })

    }
  } catch (error) {
    return res.send({ status: 0, err: error, message: "server error" })
  }
}

const getAllMeterialProductsInvAudit = async (request, reply) => {
  try {
    const { userType, userId, isVendor, page, limit, search, } = request.body
    console.log("jhdsfdf", request.body);

    // let results = await Product.getAllMaterials(true);
    const { data, total } = await Product.getAllMaterialsForInvAudit(true, page, limit, search);

    if (!empty(data)) {
      reply.send({ status: 1, results: data, total, message: "Data fetched successfully" });
    } else {
      reply.send({ status: 0, results: [], message: "Error fetching data" });
    }
  } catch (error) {
    console.log("Server error", error);
    reply.status(500).send("Server error" + error);
  }
};

const searchFinishedProductByQR = async (request, reply) => {
  try {
    const { barcode, userId, userType, isMaterial } = request.body;

    console.log("QR search input:", request.body);

    const { data, total } = await Product.searchFinishedProductByBarcode(barcode, isMaterial);

    if (!empty(data)) {
      reply.send({ status: 1, results: data, total, message: "QR search successful" });
    } else {
      reply.send({ status: 0, results: [], message: "No finished product found with this QR" });
    }
  } catch (error) {
    console.error("QR search server error:", error);
    reply.status(500).send("Server error: " + error);
  }
};

const updateMainLabelSettings = async (req, res) => {
  try {
    const { values } = req.body;
    const result = await Product.updateMainLabelSettingsDB(values);
    console.log('resultttttttttt', result)
    if (result) {
      res.send({ status: 1, result });
    } else {
      res.send({ status: 0, message: "Update failed" });
    }
  } catch (error) {
    return res.send({ status: 0, err: error.message, message: "Server error" });
  }
};

const getMainLabelSettings = async (req, res) => {
  try {
    const result = await Product.getMainLabelSettingsDB();

    if (result) {
      res.send({ status: 1, result });
    } else {
      res.send({ status: 0, message: "No settings found" });
    }
  } catch (error) {
    return res.send({ status: 0, err: error.message, message: "Server error" });
  }
};

const updateSmallLabelSettings = async (req, res) => {
  try {
    const { values } = req.body;
    const result = await Product.updateSmallLabelSettingsDB(values);

    if (result) {
      res.send({ status: 1, result });
    } else {
      res.send({ status: 0, message: "Update failed" });
    }
  } catch (error) {
    return res.send({ status: 0, err: error.message, message: "Server error" });
  }
};

const getSmallLabelSettings = async (req, res) => {
  try {
    const result = await Product.getSmallLabelSettingsDB();

    if (result) {
      res.send({ status: 1, result });
    } else {
      res.send({ status: 0, message: "No settings found" });
    }
  } catch (error) {
    return res.send({ status: 0, err: error.message, message: "Server error" });
  }
};


const fetchAllAvalaraItems = async (req, res) => {
  try {
    const accountId = AVALARA_ACCOUNT_ID;
    const licenseKey = AVALARA_LICENSE_KEY;
    const companyId = AVALARA_COMPANY_ID; // numeric ID
    const baseUrl = `${AVALARA_BASE_URL}/api/v2`;

    const auth = Buffer.from(`${accountId}:${licenseKey}`).toString('base64');
    const headers = {
      Authorization: `Basic ${auth}`,
      'Content-Type': 'application/json',
    };

    const allItems = [];
    let page = 0;
    const pageSize = 1000;

    while (true) {
      const url = `${baseUrl}/companies/${companyId}/items?$top=${pageSize}&$skip=${page * pageSize}`;
      try {
        const response = await axios.get(url, { headers });
        const items = response.data.value || [];

        if (items.length === 0) break;

        allItems.push(...items);
        page++;
      } catch (error) {
        console.error('Error fetching Avalara items:', error.response?.data || error.message);
        break;
      }
    }
    // console.log("allItemsallItemsallItems", allItems);
    res.send({ status: 1, result: allItems, message: "Items fetched successfully" });
    return allItems;
  } catch (error) {
    return res.send({ status: 0, error: error.message, message: "Server error" });
  }
};



const getRecipeById = async (req, res) => {
  try {
    const { id } = req.body;
    if (!id) {
      return res.send({ status: 0, message: 'Recipe ID is required.' });
    }

    const recipe = await Recipe.getRecipeById(id);

    if (recipe && recipe.length > 0) {
      // Strip timestamp suffix for display (if present and from split-edited)
      const cleanRecipe = recipe[0]?.isSplitEdited && /-\d{14}$/.test(recipe[0]?.name || '')
        ? { ...recipe[0], name: (recipe[0].name || '').replace(/-\d{14}$/, '') }
        : recipe[0];

      res.send({ status: 1, message: 'Recipe fetched successfully', data: cleanRecipe });
    } else {
      res.send({ status: 0, message: 'Recipe not found.' });
    }
  } catch (error) {
    console.error('Error in getRecipe:', error);
    res.send({ status: 500, message: 'An error occurred while fetching the recipe.' });
  }
};

module.exports = {
  addProduct,
  getProductById, getRecipeById, getRecipeById,
  updateProduct,
  deleteProduct,
  getAllProducts,
  addMaterialProduct,
  getAllMeterialProducts,
  getAllMeterialProductsFiltered,
  getTaxCategory,
  getLastVendorMaterialUnitCost,
  addFinishedProduct,
  getFinishedAndPartialProducts,
  getFinishedProducts,
  getPartialFinishedProducts,
  getPartialFinishedProductsStockByUnit,
  getAllFinishedProducts,
  CreateReceiveOrder,
  EditReceiveOrder,
  getAllReceiveOrders,
  getOrderById,
  updateReceiveOrder,
  deleteFinishedProduct,
  getFinishedProductById,
  editFinishedProduct,
  moveFinishedProductToPartial,
  moveFinishedProductToWaste,
  deletePartialFinishedProduct,
  refillFinishedProduct,
  cancelReceiveOrder,
  addDataSet,
  getDataSet,
  addProductCategory,
  editProductCategory,
  getAllProductCategories,
  deleteProductCategory,
  addProductSubcategory,
  getAllProductSubCategories,
  getSubCategoryById,
  editProductSubCategory,
  deleteProductSubCategory,
  updateInventory,
  fetchInventoryAuditLog,
  getSubcategoryFromCategoryId,
  getFinishedProductDataByID,
  addLabourCostServices,
  updateLabourCostServices,
  deleteLabourCostServices,
  moveLabourCostService,
  getAllLabourCostServices,
  getAllLabourCostServicesOnly,
  getAllLabourCostServicesForJobCost,
  getLabourCostServicesById,
  addLabourCostServiceType,
  updateLabourCostServiceTypes,
  deleteLabourCostServiceTypes,
  getAllLabourCostServiceTypes,
  getLabourCostServiceTypesById,
  duplicateProduct,
  getClientStockLogsController,
  uploadMaterial,
  uploadCatalog,
  checkAllMeterialStock,
  addDataToProductTableOnReq,
  getTaxCategoryById,
  getAllTaxCategory,
  addTaxCategory,
  updateTaxCategory,
  updateInhouse,
  markProductViewed,
  deleteTaxCategory,
  fetchQBCategories,
  moveMaterialToClientOwned,
  fetchQbAccount,
  getAllMeterialProductsInvAudit,
  searchFinishedProductByQR,
  transporter,
  updateMainLabelSettings,
  getMainLabelSettings,
  updateSmallLabelSettings,
  getSmallLabelSettings,
  fetchAllAvalaraItems,
  syncAllCatalogProductsToQuickBooks,
  syncAllMaterialProductsToQuickBooks,
  getAllReceiveOrdersLatestFirst,
  markLabourServiceViewed,
  getAllSalesOrdersForAutocomplete,
};
