- Remove redundant string= from 17 field definitions where name matches string value (W8113) - Convert @staticmethod to instance methods in selection methods for proper self.env._() access - Fix W8161 (prefer-env-translation) by using self.env._() instead of standalone _() - Fix W8301/W8115 (translation-not-lazy) by proper placement of % interpolation outside self.env._() - Remove unused imports of odoo._ from group_order.py and sale_order_extension.py - All OCA linting warnings in website_sale_aplicoop main models are now resolved Changes: - website_sale_aplicoop/models/group_order.py: 21 field definitions cleaned - website_sale_aplicoop/models/sale_order_extension.py: 5 field definitions cleaned + @staticmethod conversion - Consistent with OCA standards for addon submission
340 lines
13 KiB
JavaScript
340 lines
13 KiB
JavaScript
/**
|
|
* Checkout Labels Loading
|
|
* Fetches translated labels for checkout table summary
|
|
* IMPORTANT: This script waits for the cart to be loaded by website_sale.js
|
|
* before rendering the checkout summary.
|
|
*/
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
console.log("[CHECKOUT] Script loaded");
|
|
|
|
// Get order ID from button
|
|
var confirmBtn = document.getElementById("confirm-order-btn");
|
|
if (!confirmBtn) {
|
|
console.log("[CHECKOUT] No confirm button found");
|
|
return;
|
|
}
|
|
|
|
var orderId = confirmBtn.getAttribute("data-order-id");
|
|
if (!orderId) {
|
|
console.log("[CHECKOUT] No order ID found");
|
|
return;
|
|
}
|
|
|
|
console.log("[CHECKOUT] Order ID:", orderId);
|
|
|
|
// Get summary div
|
|
var summaryDiv = document.getElementById("checkout-summary");
|
|
if (!summaryDiv) {
|
|
console.log("[CHECKOUT] No summary div found");
|
|
return;
|
|
}
|
|
|
|
// Function to fetch labels and render checkout
|
|
var fetchLabelsAndRender = function () {
|
|
console.log("[CHECKOUT] Fetching labels...");
|
|
|
|
// Wait for window.groupOrderShop.labels to be initialized (contains hardcoded labels)
|
|
var waitForLabels = function (callback, maxWait = 3000, checkInterval = 50) {
|
|
var startTime = Date.now();
|
|
var checkLabels = function () {
|
|
if (
|
|
window.groupOrderShop &&
|
|
window.groupOrderShop.labels &&
|
|
Object.keys(window.groupOrderShop.labels).length > 0
|
|
) {
|
|
console.log("[CHECKOUT] ✅ Hardcoded labels found, proceeding");
|
|
callback();
|
|
} else if (Date.now() - startTime < maxWait) {
|
|
setTimeout(checkLabels, checkInterval);
|
|
} else {
|
|
console.log("[CHECKOUT] ⚠️ Timeout waiting for labels, proceeding anyway");
|
|
callback();
|
|
}
|
|
};
|
|
checkLabels();
|
|
};
|
|
|
|
waitForLabels(function () {
|
|
// Now fetch additional labels from server
|
|
// Detect current language from document or navigator
|
|
var currentLang =
|
|
document.documentElement.lang ||
|
|
document.documentElement.getAttribute("lang") ||
|
|
navigator.language ||
|
|
"es_ES";
|
|
console.log("[CHECKOUT] Detected language:", currentLang);
|
|
|
|
fetch("/eskaera/labels", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
lang: currentLang,
|
|
}),
|
|
})
|
|
.then(function (response) {
|
|
console.log("[CHECKOUT] Response status:", response.status);
|
|
return response.json();
|
|
})
|
|
.then(function (data) {
|
|
console.log("[CHECKOUT] Response data:", data);
|
|
var serverLabels = data.result || data;
|
|
console.log(
|
|
"[CHECKOUT] Server labels count:",
|
|
Object.keys(serverLabels).length
|
|
);
|
|
console.log("[CHECKOUT] Sample server labels:", {
|
|
draft_merged_success: serverLabels.draft_merged_success,
|
|
home_delivery: serverLabels.home_delivery,
|
|
});
|
|
|
|
// CRITICAL: Merge server labels with existing hardcoded labels
|
|
// Hardcoded labels MUST take precedence over server labels
|
|
if (window.groupOrderShop && window.groupOrderShop.labels) {
|
|
var existingLabels = window.groupOrderShop.labels;
|
|
console.log(
|
|
"[CHECKOUT] Existing hardcoded labels count:",
|
|
Object.keys(existingLabels).length
|
|
);
|
|
console.log("[CHECKOUT] Sample existing labels:", {
|
|
draft_merged_success: existingLabels.draft_merged_success,
|
|
home_delivery: existingLabels.home_delivery,
|
|
});
|
|
|
|
// Start with server labels, then overwrite with hardcoded ones
|
|
var mergedLabels = Object.assign({}, serverLabels);
|
|
Object.assign(mergedLabels, existingLabels);
|
|
|
|
window.groupOrderShop.labels = mergedLabels;
|
|
console.log(
|
|
"[CHECKOUT] ✅ Merged labels - final count:",
|
|
Object.keys(mergedLabels).length
|
|
);
|
|
console.log("[CHECKOUT] Verification:", {
|
|
draft_merged_success: mergedLabels.draft_merged_success,
|
|
home_delivery: mergedLabels.home_delivery,
|
|
});
|
|
} else {
|
|
// If no existing labels, use server labels as fallback
|
|
if (window.groupOrderShop) {
|
|
window.groupOrderShop.labels = serverLabels;
|
|
}
|
|
console.log("[CHECKOUT] ⚠️ No existing labels, using server labels");
|
|
}
|
|
|
|
window.renderCheckoutSummary(window.groupOrderShop.labels);
|
|
})
|
|
.catch(function (error) {
|
|
console.error("[CHECKOUT] Error:", error);
|
|
// Fallback to translated labels
|
|
window.renderCheckoutSummary(window.getCheckoutLabels());
|
|
});
|
|
});
|
|
};
|
|
|
|
// Listen for cart ready event instead of polling
|
|
if (window.groupOrderShop && window.groupOrderShop.orderId) {
|
|
// Cart already initialized, render immediately
|
|
console.log("[CHECKOUT] Cart already ready");
|
|
fetchLabelsAndRender();
|
|
} else {
|
|
// Wait for cart initialization event
|
|
console.log("[CHECKOUT] Waiting for cart ready event...");
|
|
document.addEventListener(
|
|
"groupOrderCartReady",
|
|
function () {
|
|
console.log("[CHECKOUT] Cart ready event received");
|
|
fetchLabelsAndRender();
|
|
},
|
|
{ once: true }
|
|
);
|
|
|
|
// Fallback timeout in case event never fires
|
|
setTimeout(function () {
|
|
if (window.groupOrderShop && window.groupOrderShop.orderId) {
|
|
console.log("[CHECKOUT] Fallback timeout triggered");
|
|
fetchLabelsAndRender();
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
/**
|
|
* Render order summary table or empty message
|
|
* Exposed globally so other scripts can call it
|
|
*/
|
|
window.renderCheckoutSummary = function (labels) {
|
|
labels = labels || window.getCheckoutLabels();
|
|
|
|
var summaryDiv = document.getElementById("checkout-summary");
|
|
if (!summaryDiv) return;
|
|
|
|
var cartKey =
|
|
"eskaera_" +
|
|
(document.getElementById("confirm-order-btn")
|
|
? document.getElementById("confirm-order-btn").getAttribute("data-order-id")
|
|
: "1") +
|
|
"_cart";
|
|
var cart = JSON.parse(localStorage.getItem(cartKey) || "{}");
|
|
|
|
var summaryTable = summaryDiv.querySelector(".checkout-summary-table");
|
|
var tbody = summaryDiv.querySelector("#checkout-summary-tbody");
|
|
var totalSection = summaryDiv.querySelector(".checkout-total-section");
|
|
|
|
// If no table found, create it with headers (shouldn't happen, but fallback)
|
|
if (!summaryTable) {
|
|
var html =
|
|
'<table class="table table-hover checkout-summary-table" id="checkout-summary-table" role="grid" aria-label="Purchase summary"><thead class="table-dark"><tr>' +
|
|
'<th scope="col" class="col-name">' +
|
|
escapeHtml(labels.product) +
|
|
"</th>" +
|
|
'<th scope="col" class="col-qty text-center">' +
|
|
escapeHtml(labels.quantity) +
|
|
"</th>" +
|
|
'<th scope="col" class="col-price text-right">' +
|
|
escapeHtml(labels.price) +
|
|
"</th>" +
|
|
'<th scope="col" class="col-subtotal text-right">' +
|
|
escapeHtml(labels.subtotal) +
|
|
"</th>" +
|
|
'</tr></thead><tbody id="checkout-summary-tbody"></tbody></table>' +
|
|
'<div class="checkout-total-section"><div class="total-row">' +
|
|
'<span class="total-label">' +
|
|
escapeHtml(labels.total) +
|
|
"</span>" +
|
|
'<span class="total-amount" id="checkout-total-amount">€0.00</span>' +
|
|
"</div></div>";
|
|
summaryDiv.innerHTML = html;
|
|
summaryTable = summaryDiv.querySelector(".checkout-summary-table");
|
|
tbody = summaryDiv.querySelector("#checkout-summary-tbody");
|
|
totalSection = summaryDiv.querySelector(".checkout-total-section");
|
|
}
|
|
|
|
// Clear only tbody, preserve headers
|
|
tbody.innerHTML = "";
|
|
|
|
if (Object.keys(cart).length === 0) {
|
|
// Show empty message if cart is empty
|
|
var emptyRow = document.createElement("tr");
|
|
emptyRow.id = "checkout-empty-row";
|
|
emptyRow.className = "empty-message";
|
|
emptyRow.innerHTML =
|
|
'<td colspan="4" class="text-center text-muted py-4">' +
|
|
'<i class="fa fa-inbox fa-2x mb-2"></i>' +
|
|
"<p>" +
|
|
escapeHtml(labels.empty) +
|
|
"</p>" +
|
|
"</td>";
|
|
tbody.appendChild(emptyRow);
|
|
|
|
// Hide total section
|
|
totalSection.style.display = "none";
|
|
} else {
|
|
// Hide empty row if visible
|
|
var emptyRow = tbody.querySelector("#checkout-empty-row");
|
|
if (emptyRow) emptyRow.remove();
|
|
|
|
// Get delivery product ID from page data
|
|
var checkoutPage = document.querySelector(".eskaera-checkout-page");
|
|
var deliveryProductId = checkoutPage
|
|
? checkoutPage.getAttribute("data-delivery-product-id")
|
|
: null;
|
|
|
|
// Separate normal products from delivery product
|
|
var normalProducts = [];
|
|
var deliveryProduct = null;
|
|
|
|
Object.keys(cart).forEach(function (productId) {
|
|
if (productId === deliveryProductId) {
|
|
deliveryProduct = { id: productId, item: cart[productId] };
|
|
} else {
|
|
normalProducts.push({ id: productId, item: cart[productId] });
|
|
}
|
|
});
|
|
|
|
// Sort normal products numerically
|
|
normalProducts.sort(function (a, b) {
|
|
return parseInt(a.id) - parseInt(b.id);
|
|
});
|
|
|
|
var total = 0;
|
|
|
|
// Render normal products first
|
|
normalProducts.forEach(function (product) {
|
|
var item = product.item;
|
|
var qty = parseFloat(item.quantity || item.qty || 1);
|
|
if (isNaN(qty)) qty = 1;
|
|
var price = parseFloat(item.price || 0);
|
|
if (isNaN(price)) price = 0;
|
|
var subtotal = qty * price;
|
|
total += subtotal;
|
|
|
|
var row = document.createElement("tr");
|
|
row.innerHTML =
|
|
"<td>" +
|
|
escapeHtml(item.name) +
|
|
"</td>" +
|
|
'<td class="text-center">' +
|
|
qty.toFixed(2).replace(/\.?0+$/, "") +
|
|
"</td>" +
|
|
'<td class="text-right">€' +
|
|
price.toFixed(2) +
|
|
"</td>" +
|
|
'<td class="text-right">€' +
|
|
subtotal.toFixed(2) +
|
|
"</td>";
|
|
tbody.appendChild(row);
|
|
});
|
|
|
|
// Render delivery product last if present
|
|
if (deliveryProduct) {
|
|
var item = deliveryProduct.item;
|
|
var qty = parseFloat(item.quantity || item.qty || 1);
|
|
if (isNaN(qty)) qty = 1;
|
|
var price = parseFloat(item.price || 0);
|
|
if (isNaN(price)) price = 0;
|
|
var subtotal = qty * price;
|
|
total += subtotal;
|
|
|
|
var row = document.createElement("tr");
|
|
row.innerHTML =
|
|
"<td>" +
|
|
escapeHtml(item.name) +
|
|
"</td>" +
|
|
'<td class="text-center">' +
|
|
qty.toFixed(2).replace(/\.?0+$/, "") +
|
|
"</td>" +
|
|
'<td class="text-right">€' +
|
|
price.toFixed(2) +
|
|
"</td>" +
|
|
'<td class="text-right">€' +
|
|
subtotal.toFixed(2) +
|
|
"</td>";
|
|
tbody.appendChild(row);
|
|
}
|
|
|
|
// Update total
|
|
var totalAmount = summaryDiv.querySelector("#checkout-total-amount");
|
|
if (totalAmount) {
|
|
totalAmount.textContent = "€" + total.toFixed(2);
|
|
}
|
|
|
|
// Show total section
|
|
totalSection.style.display = "block";
|
|
}
|
|
|
|
console.log("[CHECKOUT] Summary rendered");
|
|
};
|
|
|
|
/**
|
|
* Escape HTML to prevent XSS
|
|
*/
|
|
function escapeHtml(text) {
|
|
var div = document.createElement("div");
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
})();
|