You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过JS在Quote表单ribbon按钮触发模态框更新Quote Product的overed_amount字段

Answer to Your Quote Ribbon Button & Allocation Requirement

Great question! Let's break this down step by step to help you choose the right approach and provide actionable examples.

First: JavaScript vs. Plugin – Which to Choose?

JavaScript is absolutely the best fit here, and here's why:

  • Your requirement relies heavily on frontend interaction: a modal dialog for user input (category selection + percentage), which plugins can't handle directly (they run server-side with no browser context).
  • JS lets you directly interface with the CRM form context, fetch real-time data (like Quote Products for the current Quote), and update records via the Web API—all without needing to deploy server-side code.

Plugins are better suited for backend-only logic (like enforcing business rules, batch processing without user input, or integrating with external systems), but they can't display modal dialogs. Even if you wanted to use a plugin for the calculation, you'd still need JS to handle the frontend flow, so sticking with JS end-to-end is simpler.

Can HTML/JS Pass Parameters with the CRM Form?

Absolutely! You can access the current form's context (like the Quote ID, field values) using the Xrm object provided by Dynamics 365. This lets you fetch data specific to the open Quote and pass it to your custom modal logic.

Here's a quick snippet to get the current Quote's ID and fetch distinct product categories from its associated Quote Products:

// Get the current Quote's ID (remove curly braces for Web API calls)
const quoteId = Xrm.Page.data.entity.getId().replace(/[{}]/g, "");

// Fetch distinct product categories for the Quote via Web API
fetch(`/api/data/v9.2/quotedetails?$select=productcategoryid,productcategoryname&$filter=_quoteid_value eq ${quoteId}&$distinct=true`, {
  headers: {
    "OData-MaxVersion": "4.0",
    "OData-Version": "4.0",
    "Accept": "application/json",
    "Content-Type": "application/json; charset=utf-8"
  }
})
.then(response => response.json())
.then(data => {
  // Populate your dropdown with category names + GUIDs
  const dropdown = document.getElementById("categoryDropdown");
  data.value.forEach(item => {
    const option = document.createElement("option");
    option.value = item.productcategoryid;
    option.textContent = `${item.productcategoryname} (${item.productcategoryid})`;
    dropdown.appendChild(option);
  });
});

Full JS Implementation Example

Here's a complete flow for your requirement:

1. Ribbon Button Setup

  • Create a new JavaScript web resource in CRM.
  • Configure a custom ribbon button on the Quote form that calls a function (e.g., openAllocationModal()) from your web resource.

2. Custom Modal HTML (embed in your JS web resource or as a separate HTML web resource)

<div id="allocationModal" class="modal fade" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Allocate Overed Amount</h5>
        <button type="button" class="btn-close" onclick="closeModal()"></button>
      </div>
      <div class="modal-body">
        <div class="mb-3">
          <label for="categoryDropdown" class="form-label">Quote Product Category</label>
          <select id="categoryDropdown" class="form-select" required></select>
        </div>
        <div class="mb-3">
          <label for="percentageInput" class="form-label">Percentage (%)</label>
          <input type="number" id="percentageInput" class="form-control" min="0" max="100" step="0.1" required>
        </div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" onclick="closeModal()">Cancel</button>
        <button type="button" class="btn btn-primary" onclick="applyAllocation()">Apply</button>
      </div>
    </div>
  </div>
</div>

<!-- Add Bootstrap CSS/JS if you want styled modals, or use CRM's native dialogs -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

3. Core Logic Functions

// Open the modal and populate categories
function openAllocationModal() {
  const quoteId = Xrm.Page.data.entity.getId().replace(/[{}]/g, "");
  
  // Clear existing dropdown options
  const dropdown = document.getElementById("categoryDropdown");
  dropdown.innerHTML = "";

  // Fetch categories
  fetch(`/api/data/v9.2/quotedetails?$select=productcategoryid,productcategoryname&$filter=_quoteid_value eq ${quoteId}&$distinct=true`, {
    headers: {
      "OData-MaxVersion": "4.0",
      "OData-Version": "4.0",
      "Accept": "application/json"
    }
  })
  .then(response => response.json())
  .then(data => {
    data.value.forEach(item => {
      const option = document.createElement("option");
      option.value = item.productcategoryid;
      option.textContent = `${item.productcategoryname} (${item.productcategoryid})`;
      dropdown.appendChild(option);
    });
    // Show the modal
    const modal = new bootstrap.Modal(document.getElementById("allocationModal"));
    modal.show();
  })
  .catch(error => {
    Xrm.Navigation.openAlertDialog({ text: `Failed to load categories: ${error.message}` });
  });
}

// Handle allocation application
function applyAllocation() {
  const selectedCategoryId = document.getElementById("categoryDropdown").value;
  const percentage = parseFloat(document.getElementById("percentageInput").value);
  
  // Validate input
  if (!selectedCategoryId || isNaN(percentage) || percentage < 0 || percentage > 100) {
    Xrm.Navigation.openAlertDialog({ text: "Please select a valid category and enter a percentage between 0 and 100." });
    return;
  }

  const quoteId = Xrm.Page.data.entity.getId().replace(/[{}]/g, "");
  
  // Get total Quote amount
  const totalQuoteAmount = Xrm.Page.data.entity.attributes.get("totalamount").getValue();
  
  // Fetch all Quote Products in the selected category
  fetch(`/api/data/v9.2/quotedetails?$select=quotedetailid,amount&$filter=_quoteid_value eq ${quoteId} and productcategoryid eq ${selectedCategoryId}`, {
    headers: {
      "OData-MaxVersion": "4.0",
      "OData-Version": "4.0",
      "Accept": "application/json"
    }
  })
  .then(response => response.json())
  .then(products => {
    if (products.value.length === 0) {
      Xrm.Navigation.openAlertDialog({ text: "No products found in the selected category." });
      return;
    }

    // Calculate total amount for the category
    const totalCategoryAmount = products.value.reduce((sum, product) => sum + product.amount, 0);
    const batchRequests = [];

    // Build batch update requests for each product
    products.value.forEach(product => {
      const overedAmount = (totalQuoteAmount * (percentage / 100)) * (product.amount / totalCategoryAmount);
      batchRequests.push({
        method: "PATCH",
        url: `/quotedetails(${product.quotedetailid})`,
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ overed_amount: overedAmount })
      });
    });

    // Send batch request to update all products at once
    sendBatchRequest(batchRequests)
    .then(() => {
      Xrm.Navigation.openAlertDialog({ text: "Allocation applied successfully!" });
      Xrm.Page.data.refresh(); // Refresh the form to show updated values
      closeModal();
    })
    .catch(error => {
      Xrm.Navigation.openAlertDialog({ text: `Failed to apply allocation: ${error.message}` });
    });
  });
}

// Helper to close the modal
function closeModal() {
  const modal = bootstrap.Modal.getInstance(document.getElementById("allocationModal"));
  modal.hide();
}

// Helper to create and send Web API batch requests
function sendBatchRequest(requests) {
  const boundary = `batch_${Date.now()}`;
  let body = "";

  requests.forEach(req => {
    body += `--${boundary}\r\n`;
    body += `Content-Type: application/http\r\n`;
    body += `Content-Transfer-Encoding: binary\r\n\r\n`;
    body += `${req.method} ${req.url} HTTP/1.1\r\n`;
    body += `Content-Type: application/json\r\n\r\n`;
    body += `${req.body}\r\n\r\n`;
  });
  body += `--${boundary}--`;

  return fetch("/api/data/v9.2/$batch", {
    method: "POST",
    headers: {
      "OData-MaxVersion": "4.0",
      "OData-Version": "4.0",
      "Accept": "application/json",
      "Content-Type": `multipart/mixed; boundary=${boundary}`
    },
    body: body
  }).then(response => {
    if (!response.ok) throw new Error("Batch request failed");
    return response;
  });
}

Can Plugins Show Modal Dialogs?

No, plugins cannot display modal dialogs. Plugins execute on the server, so they have no access to the user's browser or frontend context. If you wanted to use a plugin for calculation logic, you'd still need JS to handle the modal and user input, then trigger the plugin via a custom action or Web API call—but this adds unnecessary complexity for your use case.

Key Technical Recommendations

  • Use Batch Requests: Updating multiple Quote Products in a single batch reduces API calls and improves performance, avoiding throttling issues.
  • Validate Input: Always check for valid user input (like percentage range, required fields) to prevent errors.
  • UX Improvements: Add loading spinners while fetching data or updating records to let users know the system is working.
  • Permissions: Ensure the user has write access to the overed_amount field on the Quote Product entity.
  • Consider PCF: If you want a more integrated, low-code solution, Power Apps Component Framework (PCF) can be used to build a custom control with modal functionality, but custom HTML/JS is simpler for this scenario.

内容的提问来源于stack exchange,提问作者alireza azadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:17:11