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
This commit is contained in:
@@ -1,5 +1,19 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.9-beta-15 - 05.05.2026
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
- Buton nou „PDF ANAF" în meniul Acțiuni: descarcă PDF-ul oficial al facturii generat direct de ANAF din XML-ul curent. Înainte de generare, ANAF validează factura — dacă apar erori, sunt afișate în loc să descarce un PDF cu informații greșite. Disponibil când serverul are configurat suport pentru API-ul ANAF.
|
||||||
|
|
||||||
|
### Modifications
|
||||||
|
- Reorganizat header-ul aplicației: butoanele secundare (Printează, Descarcă PDF, PDF ANAF, Validare ANAF) sunt grupate într-un meniu „Acțiuni ▾" pentru reducerea aglomerării. În header rămân vizibile permanent doar acțiunile principale: Alege Fișier, Factură Nouă, Stornează, Salvează XML.
|
||||||
|
- Eliminat selectorul Standard/Compact și butonul „Printează" injectate dinamic în header — opțiunile sunt acum directe în meniul Acțiuni („Printează — Standard" și „Printează — Compact"), un singur click pentru orice variantă.
|
||||||
|
- Pe mobil meniul Acțiuni se deschide pe toată lățimea ecranului sub header.
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
- Fixed: butonul „Validare ANAF" (existent în versiuni anterioare) și endpoint-ul folosit pentru transformarea XML → PDF foloseau ruta cu autentificare OAuth (`api.anaf.ro`) chiar și fără un token configurat — acum, în lipsa token-ului, se folosește ruta publică ANAF (`webservicesp.anaf.ro`) care nu necesită autentificare. Astfel funcționalitățile ANAF merg și pe servere fără token.
|
||||||
|
- Fixed: apelul anterior pentru transformarea în PDF folosea forma „skip validare" — în versiuni viitoare, când ar fi fost cablată, ar fi descărcat un PDF chiar și pentru XML-uri cu probleme. Acum validarea e implicită și erorile blochează descărcarea.
|
||||||
|
|
||||||
## 0.9-beta-14 - 05.05.2026
|
## 0.9-beta-14 - 05.05.2026
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|||||||
@@ -21,10 +21,21 @@
|
|||||||
<input type="file" id="fileInput" class="file-input" accept=".xml,.zip,application/zip,application/x-zip-compressed" multiple>
|
<input type="file" id="fileInput" class="file-input" accept=".xml,.zip,application/zip,application/x-zip-compressed" multiple>
|
||||||
<button onclick="document.getElementById('fileInput').click()" class="button">Alege Fișier XML / ZIP</button>
|
<button onclick="document.getElementById('fileInput').click()" class="button">Alege Fișier XML / ZIP</button>
|
||||||
<button onclick="window.openNewInvoiceModal()" class="button" title="Factură nouă cu numerotare automată">Factură Nouă</button>
|
<button onclick="window.openNewInvoiceModal()" class="button" title="Factură nouă cu numerotare automată">Factură Nouă</button>
|
||||||
<button id="btnDownloadPDF" onclick="window.downloadPDF()" class="button button-secondary" title="Descarcă factură în format PDF (client-side)">Descarcă PDF</button>
|
|
||||||
<button id="btnValidateAnaf" onclick="window.validateAnaf()" class="button button-success" style="display:none" title="Validare prin API ANAF (necesită receiver.php)">Validare ANAF</button>
|
|
||||||
<button onclick="handleStorno()" class="button button-danger">Stornează</button>
|
<button onclick="handleStorno()" class="button button-danger">Stornează</button>
|
||||||
<button onclick="saveXML()" class="button button-secondary">Salvează XML</button>
|
<button onclick="saveXML()" class="button button-secondary">Salvează XML</button>
|
||||||
|
<div class="actions-menu-wrapper">
|
||||||
|
<button id="btnActionsMenu" type="button" class="button button-secondary" aria-haspopup="menu" aria-expanded="false" aria-controls="actionsMenu">Acțiuni <span class="actions-menu-chevron" aria-hidden="true">▾</span></button>
|
||||||
|
<div id="actionsMenu" class="actions-menu" role="menu" aria-labelledby="btnActionsMenu" hidden>
|
||||||
|
<button type="button" role="menuitem" class="actions-menu-item" data-action="print" data-template="standard">Printează — Standard</button>
|
||||||
|
<button type="button" role="menuitem" class="actions-menu-item" data-action="print" data-template="compact">Printează — Compact</button>
|
||||||
|
<div class="actions-menu-divider" role="separator"></div>
|
||||||
|
<button id="btnDownloadPDF" type="button" role="menuitem" class="actions-menu-item" data-action="downloadPdf">Descarcă PDF</button>
|
||||||
|
<div class="actions-menu-anaf" hidden>
|
||||||
|
<button id="btnPdfAnaf" type="button" role="menuitem" class="actions-menu-item is-success" data-action="pdfAnaf" title="PDF oficial generat de ANAF din XML (necesită receiver.php)">PDF ANAF</button>
|
||||||
|
<button id="btnValidateAnaf" type="button" role="menuitem" class="actions-menu-item is-success" data-action="validateAnaf" title="Validare prin API ANAF (necesită receiver.php)">Validare ANAF</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -281,7 +292,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<footer class="app-footer">
|
<footer class="app-footer">
|
||||||
<span id="app-version">v0.9-beta-14</span>
|
<span id="app-version">v0.9-beta-15</span>
|
||||||
<a href="https://www.romfast.ro">www.romfast.ro</a>
|
<a href="https://www.romfast.ro">www.romfast.ro</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
* Pe hosting static (GitHub Pages, fără PHP), apelurile vor eșua cu eroare
|
* Pe hosting static (GitHub Pages, fără PHP), apelurile vor eșua cu eroare
|
||||||
* "receiver indisponibil" — verificați cu probeReceiver() la inițializare.
|
* "receiver indisponibil" — verificați cu probeReceiver() la inițializare.
|
||||||
*
|
*
|
||||||
* Configurare necesară în config.json (server-side):
|
* Configurare opțională în config.json (server-side):
|
||||||
* "anaf_token": "<Bearer token OAuth ANAF>" — necesar pentru validate + pdf
|
* "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):
|
* Endpoints ANAF (proxied):
|
||||||
* Validate : POST https://api.anaf.ro/prod/FCTEL/rest/validare/FACT1
|
* Validate : POST /FCTEL/rest/validare/FACT1
|
||||||
* PDF/HTML : POST https://api.anaf.ro/prod/FCTEL/rest/transformare/FACT1/DA
|
* XmlToPdf : POST /FCTEL/rest/transformare/FACT1 (validează default; întoarce PDF)
|
||||||
* CIF info : POST https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva
|
* CIF info : POST https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const RECEIVER = './receiver.php';
|
const RECEIVER = './receiver.php';
|
||||||
@@ -65,11 +66,14 @@ export async function anafValidate(xmlContent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obține vizualizarea ANAF a facturii (ZIP cu HTML).
|
* Obține PDF-ul oficial ANAF al facturii (transformare XML → PDF).
|
||||||
* Notă: ANAF /transformare returnează ZIP+HTML, nu PDF direct.
|
* ANAF /transformare/FACT1 validează XML-ul și întoarce direct PDF binary.
|
||||||
* PDF-ul real este generat client-side prin PR-PDF / html2pdf.js.
|
* Dacă XML-ul nu trece validarea, ANAF întoarce JSON cu erori (status 400).
|
||||||
|
*
|
||||||
* @param {string} xmlContent - XML ca string UTF-8
|
* @param {string} xmlContent - XML ca string UTF-8
|
||||||
* @returns {Promise<Blob>} ZIP blob
|
* @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) {
|
export async function anafPdf(xmlContent) {
|
||||||
let res;
|
let res;
|
||||||
@@ -82,10 +86,29 @@ export async function anafPdf(xmlContent) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('Receiver.php indisponibil — ' + e.message);
|
throw new Error('Receiver.php indisponibil — ' + e.message);
|
||||||
}
|
}
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`ANAF vizualizare: HTTP ${res.status}`);
|
const ct = (res.headers.get('Content-Type') || '').toLowerCase();
|
||||||
|
|
||||||
|
if (res.ok && ct.includes('application/pdf')) {
|
||||||
|
return { pdf: await res.blob() };
|
||||||
}
|
}
|
||||||
return 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'})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import JSZip from './vendor/jszip.mjs';
|
|||||||
import { validateCIF } from './validation/cif.js';
|
import { validateCIF } from './validation/cif.js';
|
||||||
import { validateIBAN } from './validation/iban.js';
|
import { validateIBAN } from './validation/iban.js';
|
||||||
import { runBRRules } from './validation/br-ro.js';
|
import { runBRRules } from './validation/br-ro.js';
|
||||||
import { probeReceiver, anafValidate, anafCifLookup } from './anaf.js';
|
import { probeReceiver, anafValidate, anafCifLookup, anafPdf } from './anaf.js';
|
||||||
import { catalogAdd, catalogSearch, catalogDelete } from './catalog.js';
|
import { catalogAdd, catalogSearch, catalogDelete } from './catalog.js';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
@@ -4572,6 +4572,63 @@ window.validateAnaf = async function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descarcă PDF-ul oficial generat de ANAF din XML-ul curent.
|
||||||
|
* Apelează endpoint-ul ANAF /transformare/FACT1 (validează implicit).
|
||||||
|
* Dacă XML-ul nu trece validarea ANAF, afișează erorile în loc să descarce.
|
||||||
|
* Necesită receiver.php disponibil pe server (proxy CORS).
|
||||||
|
*/
|
||||||
|
window.downloadPdfAnaf = async function() {
|
||||||
|
if (!currentInvoice) {
|
||||||
|
showToast('Nicio factură încărcată. Deschideți un XML eFactura mai întâi.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const btn = document.getElementById('btnPdfAnaf');
|
||||||
|
if (btn) _btnLoading(btn, 'Generare PDF ANAF...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const xmlString = _currentXMLString();
|
||||||
|
if (!xmlString) throw new Error('Nu s-a putut genera XML-ul facturii.');
|
||||||
|
|
||||||
|
const result = await anafPdf(xmlString);
|
||||||
|
|
||||||
|
if (result.errors && result.errors.length) {
|
||||||
|
// ANAF a respins XML-ul la validare; afișează primele erori în toast.
|
||||||
|
const errs = result.errors.filter(m => m.severity === 'ERROR' || m.severity === 'FATAL');
|
||||||
|
const sub = result.errors.slice(0, 3).map(m => m.message).join(' | ');
|
||||||
|
showToast(
|
||||||
|
`ANAF a respins XML-ul: ${errs.length || result.errors.length} mesaje`,
|
||||||
|
'error',
|
||||||
|
sub.slice(0, 220)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(result.pdf instanceof Blob)) {
|
||||||
|
throw new Error('Răspuns ANAF fără PDF.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descarcă blob-ul ca fișier PDF.
|
||||||
|
const inv = printHandler.collectInvoiceData();
|
||||||
|
const safeNum = String(inv.invoiceNumber || 'factura').replace(/[^a-z0-9_-]+/gi, '_');
|
||||||
|
const url = URL.createObjectURL(result.pdf);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `${safeNum}_ANAF.pdf`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
showToast('PDF ANAF descărcat.', 'success');
|
||||||
|
} catch (err) {
|
||||||
|
showToast('Eroare PDF ANAF: ' + err.message, 'error',
|
||||||
|
'Verificați că receiver.php este disponibil pe server.');
|
||||||
|
} finally {
|
||||||
|
if (btn) _btnDone(btn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caută datele firmei după CIF din câmpul supplierVAT / customerVAT.
|
* Caută datele firmei după CIF din câmpul supplierVAT / customerVAT.
|
||||||
* @param {'supplier'|'customer'} party
|
* @param {'supplier'|'customer'} party
|
||||||
@@ -4647,11 +4704,12 @@ window.lookupCif = async function(party) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Probează receiver.php la startup — dacă e disponibil, afișează butoanele ANAF.
|
// Probează receiver.php la startup — dacă e disponibil, afișează item-urile ANAF.
|
||||||
(async () => {
|
(async () => {
|
||||||
const available = await probeReceiver().catch(() => false);
|
const available = await probeReceiver().catch(() => false);
|
||||||
if (available) {
|
if (available) {
|
||||||
document.getElementById('btnValidateAnaf')?.style?.setProperty('display', '');
|
// Item-urile ANAF din meniul "Acțiuni" sunt grupate într-un wrapper hidden.
|
||||||
|
document.querySelector('.actions-menu-anaf')?.removeAttribute('hidden');
|
||||||
document.querySelectorAll('.anaf-cif-btn').forEach(b => b.style.removeProperty('display'));
|
document.querySelectorAll('.anaf-cif-btn').forEach(b => b.style.removeProperty('display'));
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@@ -5262,38 +5320,66 @@ const printHandler = new InvoicePrintHandler();
|
|||||||
|
|
||||||
// Initialize when the document is ready
|
// Initialize when the document is ready
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Add print controls to the UI
|
setupActionsMenu();
|
||||||
const headerButtonGroup = document.querySelector('.button-group');
|
});
|
||||||
if (headerButtonGroup) {
|
|
||||||
const printControls = document.createElement('div');
|
|
||||||
printControls.className = 'print-controls';
|
|
||||||
printControls.style.display = 'flex';
|
|
||||||
printControls.style.gap = '8px';
|
|
||||||
printControls.style.alignItems = 'center';
|
|
||||||
|
|
||||||
// Create template selector
|
|
||||||
const templateSelect = document.createElement('select');
|
|
||||||
templateSelect.className = 'form-input';
|
|
||||||
templateSelect.style.width = 'auto';
|
|
||||||
templateSelect.innerHTML = `
|
|
||||||
<option value="standard">Standard</option>
|
|
||||||
<option value="compact">Compact</option>
|
|
||||||
`;
|
|
||||||
templateSelect.addEventListener('change', (e) => {
|
|
||||||
printHandler.setTemplate(e.target.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create print button
|
/**
|
||||||
const printButton = document.createElement('button');
|
* Cablează meniul "Acțiuni ▾" din header: toggle, click-outside, Esc, item handlers.
|
||||||
printButton.className = 'button';
|
* Înlocuiește vechea injecție DOM pentru print controls — toate output-urile
|
||||||
printButton.onclick = () => printHandler.print();
|
* (Printează Standard/Compact, Descarcă PDF, PDF ANAF, Validare ANAF) sunt în meniu.
|
||||||
printButton.innerHTML = 'Printează';
|
*/
|
||||||
|
function setupActionsMenu() {
|
||||||
|
const trigger = document.getElementById('btnActionsMenu');
|
||||||
|
const menu = document.getElementById('actionsMenu');
|
||||||
|
if (!trigger || !menu) return;
|
||||||
|
|
||||||
// Add elements to controls
|
const open = () => {
|
||||||
printControls.appendChild(templateSelect);
|
menu.hidden = false;
|
||||||
printControls.appendChild(printButton);
|
trigger.setAttribute('aria-expanded', 'true');
|
||||||
|
};
|
||||||
// Add controls to header
|
const close = () => {
|
||||||
headerButtonGroup.appendChild(printControls);
|
menu.hidden = true;
|
||||||
}
|
trigger.setAttribute('aria-expanded', 'false');
|
||||||
});
|
};
|
||||||
|
const toggle = () => (menu.hidden ? open() : close());
|
||||||
|
|
||||||
|
trigger.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggle();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (!menu.hidden && !menu.contains(e.target) && e.target !== trigger) close();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && !menu.hidden) {
|
||||||
|
close();
|
||||||
|
trigger.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Item dispatch — fiecare buton are data-action (+ data-template pentru print).
|
||||||
|
menu.addEventListener('click', (e) => {
|
||||||
|
const item = e.target.closest('[data-action]');
|
||||||
|
if (!item) return;
|
||||||
|
const action = item.dataset.action;
|
||||||
|
close();
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'print':
|
||||||
|
printHandler.setTemplate(item.dataset.template || 'standard');
|
||||||
|
printHandler.print();
|
||||||
|
break;
|
||||||
|
case 'downloadPdf':
|
||||||
|
window.downloadPDF?.();
|
||||||
|
break;
|
||||||
|
case 'pdfAnaf':
|
||||||
|
window.downloadPdfAnaf?.();
|
||||||
|
break;
|
||||||
|
case 'validateAnaf':
|
||||||
|
window.validateAnaf?.();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
// 1. Primire XML eFactura (POST fără action) → salvare în temp/
|
// 1. Primire XML eFactura (POST fără action) → salvare în temp/
|
||||||
// 2. Proxy ANAF APIs:
|
// 2. Proxy ANAF APIs:
|
||||||
// ?action=ping — health check (no auth)
|
// ?action=ping — health check (no auth)
|
||||||
// ?action=validate — proxy validare ANAF (necesită anaf_token în config.json)
|
// ?action=validate — proxy validare ANAF (anaf_token opțional → ruta OAuth; altfel ruta publică)
|
||||||
// ?action=pdf — proxy transformare ANAF (ZIP+HTML, necesită anaf_token)
|
// ?action=pdf — proxy transformare XmlToPdf ANAF (PDF binary; anaf_token opțional)
|
||||||
// ?action=cif — lookup contribuabil după CIF (nu necesită token OAuth)
|
// ?action=cif — lookup contribuabil după CIF (nu necesită token OAuth)
|
||||||
// 3. Curățare fișiere temporare (?cleanup=xml_XXXX.xml)
|
// 3. Curățare fișiere temporare (?cleanup=xml_XXXX.xml)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -175,10 +175,21 @@ function handleAnafValidate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proxy transformare ANAF (vizualizare ZIP+HTML).
|
* Proxy transformare ANAF XmlToPdf — întoarce PDF binary direct.
|
||||||
* Notă: ANAF returnează ZIP cu fișiere HTML, nu PDF direct.
|
* Doc: https://api.anaf.ro/prod/FCTEL/rest/transformare/{standard}/{novld}
|
||||||
* Necesită: "anaf_token": "Bearer XXX" în config.json
|
* - standard = FACT1 (UBL Invoice / Credit Note)
|
||||||
* POST https://api.anaf.ro/prod/FCTEL/rest/transformare/FACT1/DA
|
* - novld absent → ANAF validează XML-ul; dacă invalid, întoarce JSON cu erori
|
||||||
|
* - novld = "DA" → skip validare (NEFOLOSIT aici — vrem validare implicită)
|
||||||
|
*
|
||||||
|
* Endpoint:
|
||||||
|
* - cu anaf_token configurat: api.anaf.ro (OAuth2)
|
||||||
|
* - fără token: webservicesp.anaf.ro (rută publică, fără auth)
|
||||||
|
*
|
||||||
|
* Răspuns:
|
||||||
|
* - PDF (application/pdf) la succes — body începe cu "%PDF-"
|
||||||
|
* - JSON cu erori validare la eșec (HTTP 200 sau 400)
|
||||||
|
*
|
||||||
|
* Frontend (anaf.js) detectează tipul după Content-Type și afișează corespunzător.
|
||||||
*/
|
*/
|
||||||
function handleAnafPdf() {
|
function handleAnafPdf() {
|
||||||
global $config;
|
global $config;
|
||||||
@@ -188,9 +199,12 @@ function handleAnafPdf() {
|
|||||||
$headers = ['Content-Type: text/plain; charset=utf-8'];
|
$headers = ['Content-Type: text/plain; charset=utf-8'];
|
||||||
if ($token) {
|
if ($token) {
|
||||||
$headers[] = "Authorization: Bearer $token";
|
$headers[] = "Authorization: Bearer $token";
|
||||||
|
$url = 'https://api.anaf.ro/prod/FCTEL/rest/transformare/FACT1';
|
||||||
|
} else {
|
||||||
|
$url = 'https://webservicesp.anaf.ro/prod/FCTEL/rest/transformare/FACT1';
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = curlPost('https://api.anaf.ro/prod/FCTEL/rest/transformare/FACT1/DA', $xmlContent, $headers);
|
$result = curlPost($url, $xmlContent, $headers);
|
||||||
|
|
||||||
if ($result['error']) {
|
if ($result['error']) {
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
@@ -198,9 +212,30 @@ function handleAnafPdf() {
|
|||||||
echo json_encode(['error' => 'cURL error: ' . $result['error']]);
|
echo json_encode(['error' => 'cURL error: ' . $result['error']]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
header('Content-Type: application/zip');
|
|
||||||
header('Content-Disposition: attachment; filename="vizualizare_anaf.zip"');
|
$body = $result['body'];
|
||||||
echo $result['body'];
|
|
||||||
|
// Sniff response type — PDF binary începe cu "%PDF-", JSON cu "{".
|
||||||
|
if (substr($body, 0, 5) === '%PDF-') {
|
||||||
|
header('Content-Type: application/pdf');
|
||||||
|
header('Content-Disposition: attachment; filename="factura_anaf.pdf"');
|
||||||
|
echo $body;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON (erori validare) sau alt răspuns text — pasează către client.
|
||||||
|
$trimmed = ltrim($body);
|
||||||
|
if ($trimmed !== '' && ($trimmed[0] === '{' || $trimmed[0] === '[')) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(400);
|
||||||
|
echo $body;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown response (HTML error page, etc.)
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(502);
|
||||||
|
echo json_encode(['error' => 'ANAF răspuns neașteptat', 'preview' => substr($body, 0, 200)]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,6 +152,114 @@ input.date-input {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
Actions overflow menu (header dropdown)
|
||||||
|
---------------------------------------------------------------------------- */
|
||||||
|
.actions-menu-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu-chevron {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1;
|
||||||
|
transition: transform 120ms ease;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnActionsMenu[aria-expanded="true"] .actions-menu-chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
right: 0;
|
||||||
|
min-width: 220px;
|
||||||
|
background-color: #1e293b; /* slate-800 — slightly lighter than header-bg slate-900 */
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.32);
|
||||||
|
padding: 4px 0;
|
||||||
|
z-index: 950;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
animation: actions-menu-in 120ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes actions-menu-in {
|
||||||
|
from { opacity: 0; transform: translateY(-4px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu-item {
|
||||||
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #f1f5f9;
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.3;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
transition: background-color 80ms ease, color 80ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu-item:hover,
|
||||||
|
.actions-menu-item:focus-visible {
|
||||||
|
background-color: rgba(255, 255, 255, 0.06);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu-item:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu-item.is-success {
|
||||||
|
color: #86efac; /* green ghost — ANAF actions */
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu-item.is-success:hover,
|
||||||
|
.actions-menu-item.is-success:focus-visible {
|
||||||
|
background-color: rgba(134, 239, 172, 0.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu-divider {
|
||||||
|
height: 1px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.10);
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu-anaf {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-menu-anaf[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.actions-menu-wrapper {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
.actions-menu {
|
||||||
|
left: var(--space-3);
|
||||||
|
right: var(--space-3);
|
||||||
|
top: auto;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
Cards / form sections
|
Cards / form sections
|
||||||
---------------------------------------------------------------------------- */
|
---------------------------------------------------------------------------- */
|
||||||
|
|||||||
Reference in New Issue
Block a user