Skip to content

Instantly share code, notes, and snippets.

@hsayed21
Created August 24, 2025 14:41
Show Gist options
  • Save hsayed21/df48bcf0dcf508f74f714f5d5132d1da to your computer and use it in GitHub Desktop.
Save hsayed21/df48bcf0dcf508f74f714f5d5132d1da to your computer and use it in GitHub Desktop.
Extract Odoo tickets and format for Excel
// ==UserScript==
// @name Odoo Ticket Extractor
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Extract Odoo tickets and format for Excel
// @author hsayed21
// @match https://www.posbank.me/web*
// @match https://posbank.me/web*
// @icon https://www.google.com/s2/favicons?sz=64&domain=posbank.me
// @grant none
// ==/UserScript==
(function() {
'use strict';
function addExtractionButton() {
if (!window.location.hash.includes('model=helpdesk.ticket') || !window.location.hash.includes('view_type=list')) {
return;
}
const button = document.createElement('button');
button.innerHTML = '📋 Extract Tickets for Excel';
button.style.cssText = `
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
background: #5a3c52;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
`;
button.addEventListener('click', extractTickets);
document.body.appendChild(button);
}
function extractTickets() {
const ticketRows = document.querySelectorAll('tr.o_data_row[data-id*="helpdesk.ticket"]');
if (ticketRows.length === 0) {
alert('No tickets found on this page. Make sure you are on the ticket list view.');
return;
}
const excelData = [];
excelData.push(['CaseId', 'Description', 'Status', 'OdooLink', 'Notes']);
ticketRows.forEach(row => {
try {
// Extract Case ID
const idCell = row.querySelector('td[name="id"]');
const caseId = idCell ? idCell.textContent.trim() : '';
// Extract Description
const nameCell = row.querySelector('td[name="display_name"]');
const descriptionLink = nameCell ? nameCell.querySelector('a') : null;
const description = descriptionLink ? descriptionLink.textContent.trim() : '';
// Extract Odoo Link
const odooLink = descriptionLink ? descriptionLink.href : '';
// Status is always "New Feature" as requested
const status = 'New Feature';
// Notes field is empty by default
const notes = '';
if (caseId) {
excelData.push([caseId, description, status, odooLink, notes]);
}
} catch (error) {
console.error('Error extracting ticket data from row:', error);
}
});
// Convert to tab-separated format for easy Excel import
const tsvContent = excelData.map(row =>
row.map(cell => `"${cell.toString().replace(/"/g, '""')}"`).join('\t')
).join('\n');
copyToClipboard(tsvContent);
showNotification(`Successfully extracted ${excelData.length - 1} tickets! Data copied to clipboard. You can now paste it directly into Excel.`);
}
function copyToClipboard(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
// Remove the temporary element
document.body.removeChild(textarea);
}
function showNotification(message) {
const notification = document.createElement('div');
notification.innerHTML = message;
notification.style.cssText = `
position: fixed;
top: 60px;
right: 10px;
z-index: 10000;
background: #28a745;
color: white;
padding: 15px 20px;
border-radius: 5px;
max-width: 300px;
font-weight: bold;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
line-height: 1.4;
`;
document.body.appendChild(notification);
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 5000);
}
// Wait for page to load and add button
function init() {
// Wait a bit for the page to fully load
setTimeout(() => {
addExtractionButton();
}, 2000);
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Also listen for navigation changes (SPA)
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
setTimeout(addExtractionButton, 2000);
}
}).observe(document, { subtree: true, childList: true });
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment