Files
romfast-website/efactura-generator/js/anaf.js
Claude Agent 3a5076d7cd sync efactura-generator -> 0.9-beta-15
- Header reorganizat cu meniu Acțiuni (overflow dropdown)
- Buton nou PDF ANAF (transformare oficială XML->PDF prin API ANAF)
- Fix endpoint ANAF: validează default + ruta publică fără auth
2026-05-05 13:58:30 +00:00

153 lines
5.8 KiB
JavaScript

/**
* js/anaf.js — Proxy ANAF APIs prin receiver.php
*
* Toate apelurile merg prin receiver.php (CORS proxy server-side).
* Pe hosting static (GitHub Pages, fără PHP), apelurile vor eșua cu eroare
* "receiver indisponibil" — verificați cu probeReceiver() la inițializare.
*
* Configurare opțională în config.json (server-side):
* "anaf_token": "<Bearer token OAuth ANAF>" — folosește ruta OAuth (api.anaf.ro)
* Fără token: receiver folosește ruta publică webservicesp.anaf.ro (fără auth).
*
* Endpoints ANAF (proxied):
* Validate : POST /FCTEL/rest/validare/FACT1
* XmlToPdf : POST /FCTEL/rest/transformare/FACT1 (validează default; întoarce PDF)
* CIF info : POST https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva
*/
const RECEIVER = './receiver.php';
/**
* Verifică dacă receiver.php este disponibil pe server.
* Returnează true dacă poate răspunde la ?action=ping.
* @returns {Promise<boolean>}
*/
export async function probeReceiver() {
try {
const res = await fetch(`${RECEIVER}?action=ping`, { method: 'GET' });
if (!res.ok) return false;
const json = await res.json().catch(() => null);
return json?.pong === true;
} catch {
return false;
}
}
/**
* Validează un XML eFactura prin API-ul ANAF (necesită Bearer token în config.json).
* @param {string} xmlContent - XML ca string UTF-8
* @returns {Promise<{valid: boolean, messages: Array<{message:string, severity:string, xpathLocation?:string}>}>}
*/
export async function anafValidate(xmlContent) {
let res;
try {
res = await fetch(`${RECEIVER}?action=validate`, {
method: 'POST',
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
body: xmlContent
});
} catch (e) {
throw new Error('Receiver.php indisponibil — ' + e.message);
}
if (!res.ok) {
let msg = `ANAF validare: HTTP ${res.status}`;
try { const t = await res.text(); if (t) msg += ' — ' + t.slice(0, 200); } catch { /* ok */ }
throw new Error(msg);
}
const data = await res.json();
// Normalizare răspuns ANAF: { Messages: [{message, severity, xpathLocation}] }
const messages = (data.Messages || data.messages || []).map(m => ({
message: m.message || m.Message || String(m),
severity: (m.severity || m.Severity || 'ERROR').toUpperCase(),
xpathLocation: m.xpathLocation || m.XpathLocation || ''
}));
const valid = messages.filter(m => m.severity === 'ERROR' || m.severity === 'FATAL').length === 0;
return { valid, messages };
}
/**
* Obține PDF-ul oficial ANAF al facturii (transformare XML → PDF).
* ANAF /transformare/FACT1 validează XML-ul și întoarce direct PDF binary.
* Dacă XML-ul nu trece validarea, ANAF întoarce JSON cu erori (status 400).
*
* @param {string} xmlContent - XML ca string UTF-8
* @returns {Promise<{pdf: Blob}|{errors: Array<{message:string,severity:string}>}>}
* - pdf: Blob `application/pdf` la succes
* - errors: listă mesaje validare la eșec
*/
export async function anafPdf(xmlContent) {
let res;
try {
res = await fetch(`${RECEIVER}?action=pdf`, {
method: 'POST',
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
body: xmlContent
});
} catch (e) {
throw new Error('Receiver.php indisponibil — ' + e.message);
}
const ct = (res.headers.get('Content-Type') || '').toLowerCase();
if (res.ok && ct.includes('application/pdf')) {
return { pdf: await res.blob() };
}
// ANAF validation errors come back as JSON (HTTP 400 or 200 with JSON body)
if (ct.includes('application/json')) {
const data = await res.json().catch(() => null);
const messages = (data?.Messages || data?.messages || []).map(m => ({
message: m.message || m.Message || String(m),
severity: (m.severity || m.Severity || 'ERROR').toUpperCase()
}));
if (messages.length) return { errors: messages };
if (data?.error) throw new Error(data.error);
}
if (!res.ok) {
const txt = await res.text().catch(() => '');
throw new Error(`ANAF transformare: HTTP ${res.status}` + (txt ? ' — ' + txt.slice(0, 200) : ''));
}
throw new Error(`ANAF transformare: răspuns neașteptat (${ct || 'fără content-type'})`);
}
/**
* Caută informații contribuabil după CIF prin ANAF.
* Folosește API-ul sincron ANAF v9 (webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva).
* Nu necesită token OAuth.
* @param {string|number} cif - CIF/CUI (cu sau fără prefix RO)
* @returns {Promise<{
* found: boolean,
* denumire?: string,
* adresa?: string,
* nrRegCom?: string,
* cui?: number,
* tvaActiv?: boolean,
* strada?: string,
* oras?: string,
* judetCod?: string,
* codPostal?: string,
* telefon?: string,
* statusEFactura?: boolean
* }>}
* @property {string} strada - Strada + număr din adresa_sediu_social ANAF
* @property {string} oras - Localitatea (fără prefix MUN./ORS./COM.)
* @property {string} judetCod - Cod județ ISO format RO-XX (ex: RO-B, RO-CJ)
* @property {string} codPostal - Cod poștal
* @property {string} telefon - Număr telefon din date_generale ANAF
* @property {boolean} statusEFactura - Înregistrat în sistemul eFactura
*/
export async function anafCifLookup(cif) {
const cifNum = String(cif).replace(/^RO\s*/i, '').trim();
let res;
try {
res = await fetch(`${RECEIVER}?action=cif&cif=${encodeURIComponent(cifNum)}`);
} catch (e) {
throw new Error('Receiver.php indisponibil — ' + e.message);
}
if (!res.ok) {
throw new Error(`ANAF CIF lookup: HTTP ${res.status}`);
}
return res.json();
}