frappe.ui.form.on('Customer', { refresh: function(frm) { // Wait for the form and its custom HTML fields to render frappe.after_ajax(() => { // Look for a button with the data attribute const $btn = frm.$wrapper.find('[data-generate-customer-assets-csv]'); if ($btn.length) { // Prevent double-binding $btn.off('click').on('click', function () { const assets = frm.doc.customer_assets || []; if (!assets.length) { frappe.msgprint('No customer assets found.'); return; } const excludedFields = [ 'owner', 'creation', 'modified', 'modified_by', 'docstatus', 'idx' ]; const fields = Object.keys(assets[0]).filter( field => !excludedFields.includes(field) ); const csvRows = []; csvRows.push(fields.join(',')); assets.forEach(row => { const csvRow = fields.map(field => { let val = row[field] || ''; return `"${String(val).replace(/"/g, '""')}"`; }); csvRows.push(csvRow.join(',')); }); const csvContent = csvRows.join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `CustomerAssets-${frm.doc.name}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); }); } }); } });