88 lines
3.8 KiB
JavaScript
88 lines
3.8 KiB
JavaScript
/**
|
|
* Billing UI — Spec 023 + VM123 deep-links (Spec 027 Fase 3)
|
|
*/
|
|
const DeskBilling = (() => {
|
|
const API = '/api';
|
|
|
|
function esc(s) {
|
|
return String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
}
|
|
|
|
async function api(path, options = {}) {
|
|
const res = await fetch(`${API}${path}`, {
|
|
...options,
|
|
headers: { ...authHeaders(), 'Content-Type': 'application/json', ...(options.headers || {}) },
|
|
});
|
|
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).detail || res.statusText);
|
|
return res.json();
|
|
}
|
|
|
|
function closeModal() {
|
|
document.querySelector('.billing-modal-backdrop')?.remove();
|
|
}
|
|
|
|
function vm123LinksHtml(vm123, acc) {
|
|
const links = vm123?.links || {};
|
|
const perms = vm123?.permissions || {};
|
|
const foss = links.foss || {};
|
|
const odoo = links.odoo || {};
|
|
const op = links.openpanel || {};
|
|
const fossHint = vm123?.foss?.client_id ? ` · cliente #${vm123.foss.client_id}` : '';
|
|
const odooHint = vm123?.odoo?.partner_name ? ` · ${vm123.odoo.partner_name}` : '';
|
|
const parts = [
|
|
`<a href="${esc(foss.url || acc.links?.fossbilling || '#')}" target="_blank" rel="noreferrer">FOSSBilling 💳${esc(fossHint)}</a>`,
|
|
`<a href="${esc(odoo.url || acc.links?.odoo || '#')}" target="_blank" rel="noreferrer">Odoo${esc(odooHint)}</a>`,
|
|
];
|
|
if (perms.can_foss_admin || perms.can_openpanel_autologin) {
|
|
parts.push(`<a href="${esc(op.url || '#')}" target="_blank" rel="noreferrer">OpenAdmin</a>`);
|
|
}
|
|
return parts.join(' · ');
|
|
}
|
|
|
|
async function openAccountModal(domain) {
|
|
closeModal();
|
|
const acc = await api(`/v1/billing/accounts/by-domain/${encodeURIComponent(domain)}`);
|
|
let vm123 = null;
|
|
try {
|
|
const q = new URLSearchParams({ domain });
|
|
if (acc.email_billing) q.set('email', acc.email_billing);
|
|
vm123 = await api(`/v1/vm123/links/client?${q}`);
|
|
} catch {
|
|
vm123 = null;
|
|
}
|
|
const canManage = typeof canManageBilling === 'function' ? canManageBilling() : canManageVm112Domains?.();
|
|
const backdrop = document.createElement('div');
|
|
backdrop.className = 'billing-modal-backdrop';
|
|
backdrop.innerHTML = `
|
|
<div class="billing-modal" role="dialog">
|
|
<h3 style="margin-top:0">Conta do cliente — ${esc(domain)}</h3>
|
|
<dl class="kv">
|
|
<dt>Estado</dt><dd>${esc(acc.billing_state)}</dd>
|
|
<dt>Razão social</dt><dd>${esc(acc.legal_name || acc.trade_name || '—')}</dd>
|
|
<dt>Email cobrança</dt><dd>${esc(acc.email_billing || '—')}</dd>
|
|
<dt>CNPJ/CPF</dt><dd>${esc(acc.tax_id || '—')}</dd>
|
|
<dt>Recorrência</dt><dd>${acc.recurrence_active ? '✅ ativa' : '—'}</dd>
|
|
</dl>
|
|
<p class="ticket-meta vm123-links">${vm123LinksHtml(vm123, acc)}</p>
|
|
<div style="display:flex;gap:0.5rem;margin-top:1rem;flex-wrap:wrap">
|
|
${canManage ? `<button type="button" class="btn btn-primary btn-sm" data-billing-ativate="${acc.id}">Activar recorrência</button>` : ''}
|
|
<button type="button" class="btn btn-sm" data-billing-close>Fechar</button>
|
|
</div>
|
|
</div>`;
|
|
document.body.appendChild(backdrop);
|
|
backdrop.addEventListener('click', (e) => { if (e.target === backdrop) closeModal(); });
|
|
backdrop.querySelector('[data-billing-close]')?.addEventListener('click', closeModal);
|
|
backdrop.querySelector('[data-billing-ativate]')?.addEventListener('click', async () => {
|
|
await api(`/v1/billing/accounts/${acc.id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ recurrence_active: true, billing_state: 'billing_active' }),
|
|
});
|
|
closeModal();
|
|
if (state.view === 'overview-home') await renderOverviewHome();
|
|
});
|
|
}
|
|
|
|
return { openAccountModal, closeModal };
|
|
})();
|
|
|
|
window.DeskBilling = DeskBilling;
|