[FIX] website_sale_aplicoop: Remove redundant string= attributes and fix OCA linting warnings

- 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
This commit is contained in:
snt 2026-02-18 17:54:43 +01:00
parent 5c89795e30
commit 6fbc7b9456
73 changed files with 5386 additions and 4354 deletions

View file

@ -5,140 +5,158 @@
* before rendering the checkout summary.
*/
(function() {
'use strict';
(function () {
"use strict";
console.log('[CHECKOUT] Script loaded');
console.log("[CHECKOUT] Script loaded");
// Get order ID from button
var confirmBtn = document.getElementById('confirm-order-btn');
var confirmBtn = document.getElementById("confirm-order-btn");
if (!confirmBtn) {
console.log('[CHECKOUT] No confirm button found');
console.log("[CHECKOUT] No confirm button found");
return;
}
var orderId = confirmBtn.getAttribute('data-order-id');
var orderId = confirmBtn.getAttribute("data-order-id");
if (!orderId) {
console.log('[CHECKOUT] No order ID found');
console.log("[CHECKOUT] No order ID found");
return;
}
console.log('[CHECKOUT] Order ID:', orderId);
console.log("[CHECKOUT] Order ID:", orderId);
// Get summary div
var summaryDiv = document.getElementById('checkout-summary');
var summaryDiv = document.getElementById("checkout-summary");
if (!summaryDiv) {
console.log('[CHECKOUT] No summary div found');
console.log("[CHECKOUT] No summary div found");
return;
}
// Function to fetch labels and render checkout
var fetchLabelsAndRender = function() {
console.log('[CHECKOUT] Fetching labels...');
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 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');
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');
console.log("[CHECKOUT] ⚠️ Timeout waiting for labels, proceeding anyway");
callback();
}
};
checkLabels();
};
waitForLabels(function() {
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',
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',
"Content-Type": "application/json",
},
body: JSON.stringify({
lang: currentLang
lang: currentLang,
}),
})
.then(function (response) {
console.log("[CHECKOUT] Response status:", response.status);
return response.json();
})
})
.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
.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,
});
// 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;
// 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");
}
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());
});
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');
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 });
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() {
setTimeout(function () {
if (window.groupOrderShop && window.groupOrderShop.orderId) {
console.log('[CHECKOUT] Fallback timeout triggered');
console.log("[CHECKOUT] Fallback timeout triggered");
fetchLabelsAndRender();
}
}, 500);
@ -148,67 +166,88 @@
* Render order summary table or empty message
* Exposed globally so other scripts can call it
*/
window.renderCheckoutSummary = function(labels) {
window.renderCheckoutSummary = function (labels) {
labels = labels || window.getCheckoutLabels();
var summaryDiv = document.getElementById('checkout-summary');
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 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');
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>' +
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-label">' +
escapeHtml(labels.total) +
"</span>" +
'<span class="total-amount" id="checkout-total-amount">€0.00</span>' +
'</div></div>';
"</div></div>";
summaryDiv.innerHTML = html;
summaryTable = summaryDiv.querySelector('.checkout-summary-table');
tbody = summaryDiv.querySelector('#checkout-summary-tbody');
totalSection = summaryDiv.querySelector('.checkout-total-section');
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 = '';
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">' +
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>';
"<p>" +
escapeHtml(labels.empty) +
"</p>" +
"</td>";
tbody.appendChild(emptyRow);
// Hide total section
totalSection.style.display = 'none';
totalSection.style.display = "none";
} else {
// Hide empty row if visible
var emptyRow = tbody.querySelector('#checkout-empty-row');
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;
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) {
Object.keys(cart).forEach(function (productId) {
if (productId === deliveryProductId) {
deliveryProduct = { id: productId, item: cart[productId] };
} else {
@ -217,14 +256,14 @@
});
// Sort normal products numerically
normalProducts.sort(function(a, b) {
normalProducts.sort(function (a, b) {
return parseInt(a.id) - parseInt(b.id);
});
var total = 0;
// Render normal products first
normalProducts.forEach(function(product) {
normalProducts.forEach(function (product) {
var item = product.item;
var qty = parseFloat(item.quantity || item.qty || 1);
if (isNaN(qty)) qty = 1;
@ -233,11 +272,20 @@
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>';
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);
});
@ -251,32 +299,41 @@
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>';
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');
var totalAmount = summaryDiv.querySelector("#checkout-total-amount");
if (totalAmount) {
totalAmount.textContent = '€' + total.toFixed(2);
totalAmount.textContent = "€" + total.toFixed(2);
}
// Show total section
totalSection.style.display = 'block';
totalSection.style.display = "block";
}
console.log('[CHECKOUT] Summary rendered');
console.log("[CHECKOUT] Summary rendered");
};
/**
* Escape HTML to prevent XSS
*/
function escapeHtml(text) {
var div = document.createElement('div');
var div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}