import { InvoiceFormatter } from './formatter.js'; // Constants const XML_NAMESPACES = { ubl: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2", cbc: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2", cac: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" }; const VAT_TYPES = { "S": "Cotă Standard", "AE": "Taxare Inversă", "O": "Neplătitor TVA", "Z": "Cotă 0% TVA", "E": "Neimpozabil" }; const VAT_EXEMPTION_CODES = { 'AE': { code: 'VATEX-EU-AE', reason: 'Taxare inversa' }, 'K': { code: 'VATEX-EU-IC', reason: 'Livrare intracomunitara' }, 'O': { code: 'VATEX-EU-O', reason: 'Neplatitor TVA' }, 'E': [ { code: '', reason: 'Scutit' }, { code: 'VATEX-EU-F', reason: 'Bunuri second hand' }, { code: 'VATEX-EU-D', reason: 'Regim special agentii de turism' } ] }; const UNIT_CODES = new Map([ ['EA', 'Bucată (EA)'], ['XPP', 'Bucată (XPP)'], ['KGM', 'Kilogram (KGM)'], ['MTR', 'Metri (MTR)'], ['LTR', 'Litru (LTR)'], ['H87', 'Bucată (H87)'], ['MTQ', 'Metri cubi (MTQ)'] ]); const ISO_3166_1_CODES = new Set([ 'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'XI', 'YE', 'YT', 'ZA', 'ZM', 'ZW' ]); const ROMANIAN_COUNTY_CODES = new Set([ 'RO-AB', 'RO-AG', 'RO-AR', 'RO-B', 'RO-BC', 'RO-BH', 'RO-BN', 'RO-BR', 'RO-BT', 'RO-BV', 'RO-BZ', 'RO-CJ', 'RO-CL', 'RO-CS', 'RO-CT', 'RO-CV', 'RO-DB', 'RO-DJ', 'RO-GJ', 'RO-GL', 'RO-GR', 'RO-HD', 'RO-HR', 'RO-IF', 'RO-IL', 'RO-IS', 'RO-MH', 'RO-MM', 'RO-MS', 'RO-NT', 'RO-OT', 'RO-PH', 'RO-SB', 'RO-SJ', 'RO-SM', 'RO-SV', 'RO-TL', 'RO-TM', 'RO-TR', 'RO-VL', 'RO-VN', 'RO-VS' ]); const CHARGE_REASON_CODES = { 'TV': 'Cheltuieli de transport', 'FC': 'Taxe transport', 'ZZZ': 'Definite reciproc' }; const ALLOWANCE_REASON_CODES = { '95': 'Reducere', '41': 'Bonus lucrări în avans', '42': 'Alt bonus', '60': 'Reducere volum', '62': 'Alte reduceri', '63': 'Reducere producător', '64': 'Din cauza războiului', '65': 'Reducere outlet nou', '66': 'Reducere mostre', '67': 'Reducere end-of-range', '68': 'Cost ambalaj returnabil', '70': 'Reducere Incoterm', '71': 'Prag vânzări', '88': 'Suprataxă/deducere materiale', '100': 'Reducere specială', '102': 'Termen lung fix', '103': 'Temporar', '104': 'Standard', '105': 'Cifră de afaceri anuală' }; // Structure for item identifications const IDENTIFICATION_TYPES = { SELLERS: { type: 'sellers', label: 'Cod Furnizor', xmlTag: 'SellersItemIdentification' }, BUYERS: { type: 'buyers', label: 'Cod Client', xmlTag: 'BuyersItemIdentification' }, STANDARD: { type: 'standard', label: 'Cod Bare', xmlTag: 'StandardItemIdentification', schemeID: '0160' }, COMMODITY: { type: 'commodity', label: 'Cod Clasificare', xmlTag: 'CommodityClassification', schemes: [ { id: 'CV', name: 'Cod Vamal' }, { id: 'TSP', name: 'Cod CPV' }, { id: 'STI', name: 'Cod NC8' } ] } }; const formatter = new InvoiceFormatter() const resolver = { lookupNamespaceURI: prefix => { const ns = { 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', 'ubl': 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2' }; return ns[prefix] || null; } }; // Global variables let currentInvoice = null; let originalTotals = null; let vatRates = new Map(); let manuallyEditedVatRows = new Set(); // Initialize event listeners document.addEventListener('DOMContentLoaded', async function() { document.getElementById('fileInput').addEventListener('change', handleFileSelect); initializeUI(); if (!currentInvoice) { currentInvoice = createEmptyInvoice(); } const totalElements = [ 'subtotal', 'totalAllowances', 'totalCharges', 'netAmount', 'vat', 'total' ]; totalElements.forEach(elementId => { const element = document.getElementById(elementId); setupInlineEditing(element); }); // Add currency code validation const currencyInputs = document.querySelectorAll('[name="documentCurrencyCode"], [name="taxCurrencyCode"]'); currencyInputs.forEach(input => { input.addEventListener('input', function(e) { // Convert to uppercase this.value = this.value.toUpperCase(); // Remove any non-letter characters this.value = this.value.replace(/[^A-Z]/g, ''); // Limit to 3 characters if (this.value.length > 3) { this.value = this.value.slice(0, 3); } }); }); // Make document currency code required const documentCurrencyInput = document.querySelector('[name="documentCurrencyCode"]'); if (documentCurrencyInput) { documentCurrencyInput.required = true; } const supplierVATInput = document.querySelector('[name="supplierVAT"]'); if (supplierVATInput) { supplierVATInput.addEventListener('change', function() { updateAllVATTypes(); }); } addExchangeRateField(); initializeLocationSelectors(); // Verifică dacă avem parametru XML în URL const urlParams = new URLSearchParams(window.location.search); const xmlFileName = urlParams.get('xml'); if (xmlFileName) { try { // Încarcă XML-ul din fișierul temporar const response = await fetch('temp/' + xmlFileName); if (response.ok) { const xmlContent = await response.text(); parseXML(xmlContent); // Curăță fișierul temporar fetch('receiver.php?cleanup=' + xmlFileName) .catch(error => console.error('Eroare la ștergerea fișierului temporar:', error)); } } catch (error) { console.error('Eroare la încărcarea XML:', error); } } }); // Initialize event listeners for existing line items document.querySelectorAll('.line-item').forEach((item, index) => { handleLineItemChange(index); }); // Inline editing setup function function setupInlineEditing(element) { let originalValue; element.addEventListener('click', function() { this.setAttribute('contenteditable', 'true'); // Store exact displayed value originalValue = this.textContent; this.focus(); }); element.addEventListener('blur', function() { this.setAttribute('contenteditable', 'false'); if (this.textContent !== originalValue) { updateTotals(); } }); element.addEventListener('keydown', function(event) { if (event.key === 'Enter') { event.preventDefault(); this.blur(); } }); } function updateTotalDisplay(elementId, value) { const element = document.getElementById(elementId); if (element) { // Convertim la număr folosind parseFloat pentru a evita probleme de truncare const numValue = parseFloat(value); element.textContent = formatter.formatCurrency(numValue); } } function displayTotals(totals) { // Actualizează toate totalurile cu formatare updateTotalDisplay('subtotal', totals.subtotal); updateTotalDisplay('totalAllowances', totals.allowances); updateTotalDisplay('totalCharges', totals.charges); updateTotalDisplay('netAmount', totals.netAmount); updateTotalDisplay('vat', totals.totalVat); updateTotalDisplay('total', totals.total); // Actualizează defalcarea TVA dacă există const container = document.getElementById('vatBreakdownRows'); if (container) { container.innerHTML = ''; if (totals.vatBreakdown) { totals.vatBreakdown.forEach((data, key) => { const [rate, type] = key.split('-'); addVATBreakdownRow( parseFloat(rate), data.baseAmount, data.vatAmount, type ); }); } } } function updateVATDisplay(row, amount, type = 'amount') { const input = row.querySelector(`.vat-${type}`); if (input) { input.value = formatter.formatCurrency(amount); } } function validateVATExemption() { const vatRows = document.querySelectorAll('.vat-row'); let isValid = true; vatRows.forEach(row => { const vatType = row.querySelector('.vat-type').value; if (['E', 'K', 'AE', 'O'].includes(vatType)) { const exemptionCodeInput = row.querySelector('.vat-exemption-code'); const exemptionReasonInput = row.querySelector('.vat-exemption-reason'); const exemptionCode = exemptionCodeInput?.value; const exemptionReason = exemptionReasonInput?.value; // Pentru neplătitori de TVA (tip O), verificăm să aibă valorile corecte if (vatType === 'O') { if (exemptionCode !== 'VATEX-EU-O') { isValid = false; exemptionCodeInput?.classList.add('invalid'); } } // Pentru celelalte tipuri de scutire, trebuie să aibă cel puțin unul dintre câmpuri completat else if (!exemptionCode && !exemptionReason) { isValid = false; exemptionCodeInput?.classList.add('invalid'); exemptionReasonInput?.classList.add('invalid'); } else { exemptionCodeInput?.classList.remove('invalid'); exemptionReasonInput?.classList.remove('invalid'); } } }); return isValid; } function addDynamicVatExemptionCode(vatType, exemptionCode, exemptionReason) { if (!vatType || !exemptionCode) return; if (vatType === 'E') { // Verifică dacă codul există deja const exists = VAT_EXEMPTION_CODES.E.some(e => e.code === exemptionCode); if (!exists) { VAT_EXEMPTION_CODES.E.push({ code: exemptionCode, reason: exemptionReason || exemptionCode }); } } else if (!VAT_EXEMPTION_CODES[vatType]) { // Adaugă un nou tip de TVA dacă nu există VAT_EXEMPTION_CODES[vatType] = { code: exemptionCode, reason: exemptionReason || exemptionCode }; } } function getDisplayValue(elementId) { const element = document.getElementById(elementId); return element ? formatter.parseCurrency(element.textContent) : 0; } // Event delegation for dynamic elements document.addEventListener('click', function(event) { const target = event.target; if (target.matches('.delete-line-item')) { const lineItem = target.closest('.line-item'); if (lineItem) { removeLineItem(parseInt(lineItem.dataset.index)); } } if (target.matches('.delete-allowance-charge')) { const charge = target.closest('.allowance-charge'); if (charge) { removeAllowanceCharge(parseInt(charge.dataset.index)); } } }); // File handling functions function handleFileSelect(event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { const xmlContent = e.target.result; parseXML(xmlContent); }; reader.readAsText(file); } } function parseXML(xmlContent) { try { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlContent, "text/xml"); const parserError = xmlDoc.querySelector('parsererror'); if (parserError) { throw new Error('Eroare la parsarea XML: ' + parserError.textContent); } // Extrage și adaugă codurile de scutire din TaxTotal const taxSubtotals = xmlDoc.querySelectorAll('cac\\:TaxSubtotal, TaxSubtotal'); taxSubtotals.forEach(subtotal => { const taxCategory = subtotal.querySelector('cac\\:TaxCategory, TaxCategory'); if (taxCategory) { const vatType = getXMLValue(taxCategory, 'cbc\\:ID, ID'); const exemptionCode = getXMLValue(taxCategory, 'cbc\\:TaxExemptionReasonCode, TaxExemptionReasonCode'); const exemptionReason = getXMLValue(taxCategory, 'cbc\\:TaxExemptionReason, TaxExemptionReason'); if (vatType && exemptionCode) { addDynamicVatExemptionCode(vatType, exemptionCode, exemptionReason); } } }); currentInvoice = xmlDoc; manuallyEditedVatRows.clear(); populateBasicDetails(xmlDoc); populatePartyDetails(xmlDoc); populateAllowanceCharges(xmlDoc); populateLineItems(xmlDoc); storeOriginalTotals(xmlDoc); restoreOriginalTotals(); displayVATBreakdown(xmlDoc); } catch (error) { handleError(error, 'Eroare la parsarea fișierului XML'); } } // Create allowance charge HTML function createAllowanceChargeHTML(index, charge) { return `
`; } function createReasonCodeOptions(isCharge, selectedCode = '') { const codes = isCharge ? CHARGE_REASON_CODES : ALLOWANCE_REASON_CODES; return Object.entries(codes) .map(([code, description]) => `` ).join(''); } window.updateReasonCodeOptions = function(index) { const chargeTypeSelect = document.querySelector(`[name="chargeType${index}"]`); const reasonCodeSelect = document.querySelector(`[name="chargeReasonCode${index}"]`); const reasonInput = document.querySelector(`[name="chargeReason${index}"]`); const isCharge = chargeTypeSelect.value === 'true'; reasonCodeSelect.innerHTML = createReasonCodeOptions(isCharge); // Update reason text based on selected code const selectedCode = reasonCodeSelect.value; const codes = isCharge ? CHARGE_REASON_CODES : ALLOWANCE_REASON_CODES; reasonInput.value = codes[selectedCode] || ''; } // Create line item HTML function createLineItemHTML(index, description = '', quantity = '1', price = '0', vatRate = '19', unitCode = 'EA', vatTypeId = 'S', itemDescription = '', lineDiscount = '0', discountReasonCode = '') { return `
`; } function createLineDiscountReasonOptions(selectedCode = '') { return Object.entries(ALLOWANCE_REASON_CODES) .map(([code, description]) => `` ).join(''); } // Add VAT breakdown row function addVATBreakdownRow(rate, baseAmount, vatAmount, vatType = 'S', existingRowId = null, exemptionCode = '', exemptionReason = '') { const container = document.getElementById('vatBreakdownRows'); const rowId = existingRowId || `vat-row-${Date.now()}`; const rowHtml = `
%
`; container.insertAdjacentHTML('beforeend', rowHtml); // Add event listener for VAT type changes const vatTypeSelect = document.querySelector(`#${rowId} .vat-type`); vatTypeSelect.addEventListener('change', () => { const exemptionContainer = document.querySelector(`#${rowId} .vat-exemption`); const newVatType = vatTypeSelect.value; if (['E', 'K', 'O', 'AE'].includes(newVatType)) { exemptionContainer.classList.remove('hidden'); // Update exemption code options const codeSelect = document.querySelector(`#${rowId} .vat-exemption-code`); codeSelect.innerHTML = generateExemptionCodeOptions(newVatType); // Set default values const defaultExemption = getDefaultExemption(newVatType); if (defaultExemption) { codeSelect.value = defaultExemption.code; document.querySelector(`#${rowId} .vat-exemption-reason`).value = defaultExemption.reason; } } else { exemptionContainer.classList.add('hidden'); } }); } function generateExemptionCodeOptions(vatType, selectedCode = '') { if (vatType === 'E') { return VAT_EXEMPTION_CODES.E.map(exemption => `` ).join(''); } else if (vatType === 'K' || vatType === 'AE' || vatType === 'O') { const exemption = VAT_EXEMPTION_CODES[vatType]; return ``; } return ''; } function getDefaultExemption(vatType) { if (vatType === 'E') { return VAT_EXEMPTION_CODES.E[0]; } return VAT_EXEMPTION_CODES[vatType]; } // Add this to window object window.updateExemptionReason = function(rowId) { const row = document.getElementById(rowId); if (!row) return; const vatType = row.querySelector('.vat-type').value; const codeSelect = row.querySelector('.vat-exemption-code'); const reasonInput = row.querySelector('.vat-exemption-reason'); if (vatType === 'E') { const selectedExemption = VAT_EXEMPTION_CODES.E.find(e => e.code === codeSelect.value); if (selectedExemption) { reasonInput.value = selectedExemption.reason; } } else if (vatType === 'K' || vatType === 'AE') { reasonInput.value = VAT_EXEMPTION_CODES[vatType].reason; } }; // Toggle optional details window.toggleOptionalDetails = function(index) { const optionalDetails = document.getElementById(`optionalDetails${index}`); const button = optionalDetails.previousElementSibling.querySelector('button'); if (optionalDetails.style.display === 'none') { optionalDetails.style.display = 'block'; button.innerHTML = '▲ Detalii Suplimentare'; } else { optionalDetails.style.display = 'none'; button.innerHTML = '▼ Detalii Suplimentare'; } } // Form validation function validateForm(silent = false) { let isValid = true; let firstInvalidField = null; // Validare câmpuri existente... const requiredFields = [ 'invoiceNumber', 'issueDate', 'dueDate', 'supplierName', 'supplierVAT', 'customerName', ]; requiredFields.forEach(fieldName => { const field = document.querySelector(`[name="${fieldName}"]`); if (!field || !field.value.trim()) { field.classList.add('invalid'); isValid = false; if (!firstInvalidField) firstInvalidField = field; } else { field.classList.remove('invalid'); } }); // Adaugă validarea TVA const vatExemptionValid = validateVATExemption(); if (!vatExemptionValid) { isValid = false; if (!silent) { alert('Vă rugăm să completați codul și/sau motivul scutirii de TVA pentru toate categoriile care necesită această informație.'); } } // Restul validărilor existente... const lineItems = document.querySelectorAll('.line-item'); if (lineItems.length === 0) { isValid = false; if (!silent) { alert('Este necesară cel puțin o linie în factură'); } return false; } lineItems.forEach((item, index) => { const quantity = parseFloat(document.querySelector(`[name="quantity${index}"]`).value); const price = parseFloat(document.querySelector(`[name="price${index}"]`).value); const description = document.querySelector(`[name="description${index}"]`).value; if (!description.trim()) { document.querySelector(`[name="description${index}"]`).classList.add('invalid'); isValid = false; } if (isNaN(quantity)) { document.querySelector(`[name="quantity${index}"]`).classList.add('invalid'); isValid = false; } if (isNaN(price)) { document.querySelector(`[name="price${index}"]`).classList.add('invalid'); isValid = false; } }); const dateInputs = document.querySelectorAll('.date-input'); dateInputs.forEach(input => { if (!validateDateInput(input)) { isValid = false; if (!firstInvalidField) firstInvalidField = input; } }); // Validare curs valutar dacă este cazul const taxCurrencyCode = document.querySelector('[name="taxCurrencyCode"]').value.trim(); const documentCurrencyCode = document.querySelector('[name="documentCurrencyCode"]').value.trim(); if (taxCurrencyCode && taxCurrencyCode !== documentCurrencyCode) { const exchangeRate = document.querySelector('[name="exchangeRate"]'); if (!exchangeRate || !exchangeRate.value || parseFloat(exchangeRate.value) <= 0) { exchangeRate.classList.add('invalid'); isValid = false; if (!firstInvalidField) { firstInvalidField = exchangeRate; } } else { exchangeRate.classList.remove('invalid'); } } if (!isValid && !silent) { if (firstInvalidField) { firstInvalidField.focus(); } alert('Vă rugăm să completați toate câmpurile obligatorii'); } return isValid; } function handleError(error, message) { console.error(message, error); alert(`${message}\nVă rugăm să verificați consola pentru detalii.`); } function formatDateToRomanian(date) { const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); return `${day}.${month}.${year}`; } function parseRomanianDate(dateStr) { const [day, month, year] = dateStr.split('.'); return `${year}-${month}-${day}`; } function createDatePicker(input, button) { const picker = new Pikaday({ field: input, trigger: button, format: 'DD.MM.YYYY', i18n: { previousMonth: 'Luna anterioară', nextMonth: 'Luna următoare', months: ['Ianuarie', 'Februarie', 'Martie', 'Aprilie', 'Mai', 'Iunie', 'Iulie', 'August', 'Septembrie', 'Octombrie', 'Noiembrie', 'Decembrie'], weekdays: ['Duminică', 'Luni', 'Marți', 'Miercuri', 'Joi', 'Vineri', 'Sâmbătă'], weekdaysShort: ['Dum', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sâm'] }, firstDay: 1, onSelect: function(date) { input.value = formatDateToRomanian(date); validateDateInput(input); } }); return picker; } function validateDateInput(input) { const value = input.value; const regex = /^(\d{2})\.(\d{2})\.(\d{4})$/; const match = value.match(regex); if (match) { const day = parseInt(match[1]); const month = parseInt(match[2]); const year = parseInt(match[3]); // Create date object and verify if it's valid const date = new Date(year, month - 1, day); if (date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day) { input.classList.remove('invalid'); return true; } } if (value !== '') { input.classList.add('invalid'); } return false; } function restrictDateInput(input) { input.addEventListener('input', function(e) { let value = e.target.value; // Remove any non-digit characters except dots value = value.replace(/[^\d.]/g, ''); // Auto-add dots after day and month if (value.length >= 2 && value.charAt(2) !== '.') { value = value.slice(0, 2) + '.' + value.slice(2); } if (value.length >= 5 && value.charAt(5) !== '.') { value = value.slice(0, 5) + '.' + value.slice(5); } // Restrict to exactly 10 characters (dd.mm.yyyy) value = value.slice(0, 10); e.target.value = value; }); input.addEventListener('blur', function() { validateDateInput(input); }); } function createCountryOptions() { return Array.from(ISO_3166_1_CODES).map(code => `` ).join(''); } function createCountyOptions() { return Array.from(ROMANIAN_COUNTY_CODES).map(code => { const label = code.replace('RO-', ''); const counties = { 'AB': 'Alba', 'AR': 'Arad', 'AG': 'Argeș', 'BC': 'Bacău', 'BH': 'Bihor', 'BN': 'Bistrița-Năsăud', 'BT': 'Botoșani', 'BV': 'Brașov', 'BR': 'Brăila', 'B': 'București', 'BZ': 'Buzău', 'CS': 'Caraș-Severin', 'CL': 'Călărași', 'CJ': 'Cluj', 'CT': 'Constanța', 'CV': 'Covasna', 'DB': 'Dâmbovița', 'DJ': 'Dolj', 'GL': 'Galați', 'GR': 'Giurgiu', 'GJ': 'Gorj', 'HR': 'Harghita', 'HD': 'Hunedoara', 'IL': 'Ialomița', 'IS': 'Iași', 'IF': 'Ilfov', 'MM': 'Maramureș', 'MH': 'Mehedinți', 'MS': 'Mureș', 'NT': 'Neamț', 'OT': 'Olt', 'PH': 'Prahova', 'SM': 'Satu Mare', 'SJ': 'Sălaj', 'SB': 'Sibiu', 'SV': 'Suceava', 'TR': 'Teleorman', 'TM': 'Timiș', 'TL': 'Tulcea', 'VS': 'Vaslui', 'VL': 'Vâlcea', 'VN': 'Vrancea' }; return ``; }).join(''); } function initializeLocationSelectors() { // Replace country inputs with selects for both supplier and customer ['supplier', 'customer'].forEach(party => { const countryInput = document.querySelector(`[name="${party}Country"]`); if (countryInput) { const select = document.createElement('select'); select.className = 'form-input'; select.name = countryInput.name; select.innerHTML = createCountryOptions(); select.value = countryInput.dataset.xmlValue || countryInput.value || 'RO'; countryInput.parentNode?.replaceChild(select, countryInput); } const countyInput = document.querySelector(`[name="${party}CountrySubentity"]`); if (countyInput) { const select = document.createElement('select'); select.className = 'form-input'; select.name = countyInput.name; select.innerHTML = createCountyOptions(); select.value = countyInput.dataset.xmlValue || countyInput.value || ''; countyInput.parentNode?.replaceChild(select, countyInput); } }); // Initialize location handlers for both parties setupPartyLocationHandlers('supplier'); setupPartyLocationHandlers('customer'); } function updateCountyVisibility(countrySelect, countySelect) { if (!countrySelect || !countySelect) return; const showCounty = countrySelect.value === 'RO'; countySelect.style.display = showCounty ? 'block' : 'none'; countySelect.required = showCounty; } function setupPartyLocationHandlers(party) { const countrySelect = document.querySelector(`[name="${party}Country"]`); const countySelect = document.querySelector(`[name="${party}CountrySubentity"]`); const cityContainer = document.querySelector(`[name="${party}City"]`)?.parentNode; if (!countrySelect || !countySelect || !cityContainer) return; const handleLocationChange = () => { const isBucharest = countrySelect.value === 'RO' && countySelect.value === 'RO-B'; const currentElement = cityContainer.querySelector('input, select'); const isCurrentlySector = currentElement.tagName.toLowerCase() === 'select'; if (isBucharest && !isCurrentlySector) { const sectorSelect = document.createElement('select'); sectorSelect.className = 'form-input'; sectorSelect.name = `${party}City`; sectorSelect.innerHTML = ` `; // Try to preserve any existing sector value const currentValue = currentElement.value || ''; if (currentValue.toUpperCase().includes('SECTOR')) { sectorSelect.value = currentValue.toUpperCase().replace(/\s+/g, ''); } cityContainer.replaceChild(sectorSelect, currentElement); } else if (!isBucharest && isCurrentlySector) { const cityInput = document.createElement('input'); cityInput.type = 'text'; cityInput.className = 'form-input'; cityInput.name = `${party}City`; cityInput.value = ''; cityContainer.replaceChild(cityInput, currentElement); } }; // Set up event listeners countrySelect.addEventListener('change', () => { updateCountyVisibility(countrySelect, countySelect); handleLocationChange(); }); countySelect.addEventListener('change', handleLocationChange); // Initial setup updateCountyVisibility(countrySelect, countySelect); handleLocationChange(); } function initializeUI() { document.querySelectorAll('.form-input').forEach(input => { input.addEventListener('input', function() { this.classList.remove('invalid'); updateTotals(); }); }); document.addEventListener('keydown', function(event) { if (event.ctrlKey || event.metaKey) { switch (event.key.toLowerCase()) { case 's': event.preventDefault(); saveXML(); break; case 'o': event.preventDefault(); document.getElementById('fileInput').click(); break; case 'n': event.preventDefault(); addLineItem(); break; } } }); // Initialize date pickers const dateInputs = document.querySelectorAll('.date-input'); dateInputs.forEach(input => { const button = input.parentElement.querySelector('.calendar-button'); createDatePicker(input, button); restrictDateInput(input); }); if (!currentInvoice) { const today = new Date(); const dueDate = new Date(); dueDate.setDate(today.getDate() + 30); document.querySelector('[name="issueDate"]').value = formatDateToRomanian(today); document.querySelector('[name="dueDate"]').value = formatDateToRomanian(dueDate); } // Add note counter event listener const noteInput = document.querySelector('[name="invoiceNote"]'); if (noteInput) { noteInput.addEventListener('input', updateNoteCounter); updateNoteCounter(); // Initial count } // Initialize location selectors initializeLocationSelectors(); window.addLineItem = addLineItem; window.removeLineItem = removeLineItem; window.addAllowanceCharge = addAllowanceCharge; window.removeAllowanceCharge = removeAllowanceCharge; window.handleStorno = handleStorno; window.updateTotals = updateTotals; window.saveXML = saveXML; window.refreshTotals = refreshTotals; window.displayVATBreakdown = displayVATBreakdown; } // Handling VAT type changes window.handleVatTypeChange = function(index) { const vatTypeSelect = document.querySelector(`[name="vatType${index}"]`); const vatRateInput = document.querySelector(`[name="vatRate${index}"]`); // Verifică dacă furnizorul este neplătitor TVA if (!isVATRegistered()) { vatTypeSelect.value = 'O'; vatTypeSelect.disabled = true; // Dezactivează selectul pentru neplătitori vatRateInput.value = '0'; vatRateInput.disabled = true; // Setează codul și motivul scutirii pentru neplătitori const row = document.querySelector(`.vat-row`); if (row) { const exemptionCode = row.querySelector('.vat-exemption-code'); const exemptionReason = row.querySelector('.vat-exemption-reason'); if (exemptionCode) exemptionCode.value = 'VATEX-EU-O'; if (exemptionReason) exemptionReason.value = 'Operațiune efectuată de neplătitor de TVA'; } } else { vatTypeSelect.disabled = false; // Activează selectul pentru plătitori switch(vatTypeSelect.value) { case 'O': // Pentru neplătitori vatRateInput.value = '0'; vatRateInput.disabled = true; break; case 'AE': // Taxare inversă case 'Z': // Cotă 0% case 'E': // Scutit vatRateInput.value = '0'; vatRateInput.disabled = true; break; case 'S': // Standard vatRateInput.value = '19'; vatRateInput.disabled = false; break; } } updateTotals(); } function isVATRegistered() { const supplierVAT = document.querySelector('[name="supplierVAT"]').value.trim().toUpperCase(); return supplierVAT.startsWith('RO'); } function handleChargeVatTypeChange(index) { const vatTypeSelect = document.querySelector(`[name="chargeVatType${index}"]`); const vatRateInput = document.querySelector(`[name="chargeVatRate${index}"]`); if (!vatTypeSelect || !vatRateInput) return; // Verifică dacă furnizorul este neplătitor TVA if (!isVATRegistered()) { vatTypeSelect.value = 'O'; vatTypeSelect.disabled = true; // Dezactivează selectul pentru neplătitori vatRateInput.value = '0'; vatRateInput.disabled = true; // Setează codul și motivul scutirii pentru neplătitori const row = document.querySelector(`.vat-row`); if (row) { const exemptionCode = row.querySelector('.vat-exemption-code'); const exemptionReason = row.querySelector('.vat-exemption-reason'); if (exemptionCode) exemptionCode.value = 'VATEX-EU-O'; if (exemptionReason) exemptionReason.value = 'Operațiune efectuată de neplătitor de TVA'; } } else { vatTypeSelect.disabled = false; // Activează selectul pentru plătitori switch(vatTypeSelect.value) { case 'O': // Pentru neplătitori vatRateInput.value = '0'; vatRateInput.disabled = true; break; case 'AE': // Taxare inversă case 'Z': // Cotă 0% case 'E': // Scutit vatRateInput.value = '0'; vatRateInput.disabled = true; break; case 'S': // Standard vatRateInput.value = '19'; vatRateInput.disabled = false; break; } } // Clear manual edits and refresh manuallyEditedVatRows.clear(); refreshTotals(); } // Funcție pentru actualizarea tuturor categoriilor TVA când se modifică codul fiscal function updateAllVATTypes() { const isNotVATRegistered = !isVATRegistered(); // Actualizează toate liniile de articole document.querySelectorAll('.line-item').forEach((item, index) => { const vatTypeSelect = item.querySelector(`[name="vatType${index}"]`); const vatRateInput = item.querySelector(`[name="vatRate${index}"]`); if (isNotVATRegistered) { vatTypeSelect.value = 'O'; vatTypeSelect.disabled = true; vatRateInput.value = '0'; vatRateInput.disabled = true; } else { vatTypeSelect.disabled = false; // Restabilește valorile implicite pentru plătitori if (vatTypeSelect.value === 'O') { vatTypeSelect.value = 'S'; vatRateInput.value = '19'; vatRateInput.disabled = false; } } }); // Actualizează breakdown-ul TVA updateVATBreakdown(); refreshTotals(); } // XML modifications function addUnitCode(code) { if (!UNIT_CODES.has(code)) { UNIT_CODES.set(code, `${code} (${code})`); } } function createUnitCodeOptionsHTML(selectedCode = 'EA') { return Array.from(UNIT_CODES.entries()) .map(([code, description]) => `` ) .join(''); } function storeOriginalTotals(xmlDoc) { const taxTotal = xmlDoc.querySelector('cac\\:TaxTotal, TaxTotal'); const monetaryTotal = xmlDoc.querySelector('cac\\:LegalMonetaryTotal, LegalMonetaryTotal'); originalTotals = { subtotal: getXMLValue(monetaryTotal, 'cbc\\:LineExtensionAmount, LineExtensionAmount'), allowances: getXMLValue(monetaryTotal, 'cbc\\:AllowanceTotalAmount, AllowanceTotalAmount', '0'), charges: getXMLValue(monetaryTotal, 'cbc\\:ChargeTotalAmount, ChargeTotalAmount', '0'), netAmount: getXMLValue(monetaryTotal, 'cbc\\:TaxExclusiveAmount, TaxExclusiveAmount'), totalVat: getXMLValue(taxTotal, 'cbc\\:TaxAmount, TaxAmount'), total: getXMLValue(monetaryTotal, 'cbc\\:TaxInclusiveAmount, TaxInclusiveAmount') }; // console.log('Original totals from XML:', originalTotals); const vatBreakdown = []; const taxSubtotals = xmlDoc.querySelectorAll('cac\\:TaxSubtotal, TaxSubtotal'); taxSubtotals.forEach(subtotal => { const taxCategory = subtotal.querySelector('cac\\:TaxCategory, TaxCategory'); vatBreakdown.push({ taxableAmount: getXMLValue(subtotal, 'cbc\\:TaxableAmount, TaxableAmount'), taxAmount: getXMLValue(subtotal, 'cbc\\:TaxAmount, TaxAmount'), percent: getXMLValue(taxCategory, 'cbc\\:Percent, Percent'), type: getXMLValue(taxCategory, 'cbc\\:ID, ID', 'S'), exemptionCode: getXMLValue(taxCategory, 'cbc\\:TaxExemptionReasonCode, TaxExemptionReasonCode'), exemptionReason: getXMLValue(taxCategory, 'cbc\\:TaxExemptionReason, TaxExemptionReason') }); }); originalTotals.vatBreakdown = vatBreakdown; } function restoreOriginalTotals() { if (!originalTotals) return; // Display exact values from XML with formatting document.getElementById('subtotal').textContent = formatter.formatCurrency(originalTotals.subtotal); document.getElementById('totalAllowances').textContent = formatter.formatCurrency(originalTotals.allowances); document.getElementById('totalCharges').textContent = formatter.formatCurrency(originalTotals.charges); document.getElementById('netAmount').textContent = formatter.formatCurrency(originalTotals.netAmount); document.getElementById('vat').textContent = formatter.formatCurrency(originalTotals.totalVat); // Bypass formatting for total document.getElementById('total').textContent = originalTotals.total; const container = document.getElementById('vatBreakdownRows'); if (container) { container.innerHTML = ''; if (originalTotals.vatBreakdown && originalTotals.vatBreakdown.length > 0) { originalTotals.vatBreakdown.forEach(vat => { addVATBreakdownRow( vat.percent, vat.taxableAmount, vat.taxAmount, vat.type, null, vat.exemptionCode, vat.exemptionReason ); }); } } } function updateNoteCounter() { const noteInput = document.querySelector('[name="invoiceNote"]'); const counter = document.querySelector('.note-counter'); if (noteInput && counter) { const length = noteInput.value.length; counter.textContent = `${length}/900 caractere`; } } function splitNoteIntoChunks(text, maxLength) { if (!text) return []; const chunks = []; let remainingText = text; while (remainingText.length > 0) { if (remainingText.length <= maxLength) { chunks.push(remainingText); break; } let splitPoint = remainingText.substr(0, maxLength).lastIndexOf('\n'); if (splitPoint === -1) { splitPoint = remainingText.substr(0, maxLength).lastIndexOf(' '); } if (splitPoint === -1) splitPoint = maxLength; chunks.push(remainingText.substr(0, splitPoint)); remainingText = remainingText.substr(splitPoint + 1); } return chunks.filter(chunk => chunk.trim().length > 0); } function populateBasicDetails(xmlDoc) { document.querySelector('[name="invoiceNumber"]').value = getXMLValue(xmlDoc, 'cbc\\:ID, ID'); // Get and combine all Note elements const notes = xmlDoc.querySelectorAll('cbc\\:Note, Note'); const combinedNotes = Array.from(notes).map(note => note.textContent).join('\n'); document.querySelector('[name="invoiceNote"]').value = combinedNotes; updateNoteCounter(); const issueDate = getXMLValue(xmlDoc, 'cbc\\:IssueDate, IssueDate'); const dueDate = getXMLValue(xmlDoc, 'cbc\\:DueDate, DueDate'); if (issueDate) { const [year, month, day] = issueDate.split('-'); document.querySelector('[name="issueDate"]').value = `${day}.${month}.${year}`; } if (dueDate) { const [year, month, day] = dueDate.split('-'); document.querySelector('[name="dueDate"]').value = `${day}.${month}.${year}`; } // Add currency code handling const documentCurrencyCode = getXMLValue(xmlDoc, 'cbc\\:DocumentCurrencyCode, DocumentCurrencyCode', 'RON'); const taxCurrencyCode = getXMLValue(xmlDoc, 'cbc\\:TaxCurrencyCode, TaxCurrencyCode', ''); document.querySelector('[name="documentCurrencyCode"]').value = documentCurrencyCode; document.querySelector('[name="taxCurrencyCode"]').value = taxCurrencyCode; // Store original totals and display them storeOriginalTotals(xmlDoc); restoreOriginalTotals(); } function populatePartyDetails(xmlDoc) { function extractPartyDetails(party, prefix) { // Extract contact information const contact = party.querySelector('cac\\:Contact, Contact'); const phone = contact?.querySelector('cbc\\:Telephone, Telephone')?.textContent || ''; const contactName = contact?.querySelector('cbc\\:Name, Name')?.textContent || ''; const email = contact?.querySelector('cbc\\:ElectronicMail, ElectronicMail')?.textContent || ''; // Country Code Extraction const countryCodeElement = party.querySelector('cac\\:Country cbc\\:IdentificationCode, Country IdentificationCode'); const countryCode = countryCodeElement ? countryCodeElement.textContent.trim() : 'RO'; // Postal Address Details const postalAddress = party.querySelector('cac\\:PostalAddress, PostalAddress'); const streetName = postalAddress ? getXMLValue(postalAddress, 'cbc\\:StreetName, StreetName') : ''; const cityName = postalAddress ? getXMLValue(postalAddress, 'cbc\\:CityName, CityName') : ''; const countyCode = postalAddress ? getXMLValue(postalAddress, 'cbc\\:CountrySubentity, CountrySubentity') : ''; // Set inputs document.querySelector(`[name="${prefix}Name"]`).value = getXMLValue(party, 'cac\\:PartyLegalEntity cbc\\:RegistrationName, PartyLegalEntity RegistrationName'); document.querySelector(`[name="${prefix}VAT"]`).value = getXMLValue(party, 'cac\\:PartyTaxScheme cbc\\:CompanyID, PartyTaxScheme CompanyID'); document.querySelector(`[name="${prefix}CompanyId"]`).value = getXMLValue(party, 'cac\\:PartyLegalEntity cbc\\:CompanyID, PartyLegalEntity CompanyID'); document.querySelector(`[name="${prefix}Address"]`).value = streetName; document.querySelector(`[name="${prefix}City"]`).value = cityName; document.querySelector(`[name="${prefix}Phone"]`).value = phone; document.querySelector(`[name="${prefix}ContactName"]`).value = contactName; document.querySelector(`[name="${prefix}Email"]`).value = email; // Country Select const countrySelect = document.querySelector(`[name="${prefix}Country"]`); if (countrySelect) { countrySelect.value = countryCode; countrySelect.dataset.xmlValue = countryCode; } // County Select const countySelect = document.querySelector(`[name="${prefix}CountrySubentity"]`); if (countySelect) { countySelect.value = countyCode; countySelect.dataset.xmlValue = countyCode; } } const supplierParty = xmlDoc.querySelector('cac\\:AccountingSupplierParty, AccountingSupplierParty'); if (supplierParty) { const supplierPartyDetails = supplierParty.querySelector('cac\\:Party, Party'); if (supplierPartyDetails) { extractPartyDetails(supplierPartyDetails, 'supplier'); } } const customerParty = xmlDoc.querySelector('cac\\:AccountingCustomerParty, AccountingCustomerParty'); if (customerParty) { const customerPartyDetails = customerParty.querySelector('cac\\:Party, Party'); if (customerPartyDetails) { extractPartyDetails(customerPartyDetails, 'customer'); } } initializeLocationSelectors(); } function populateAllowanceCharges(xmlDoc) { const charges = parseAllowanceCharges(xmlDoc); displayAllowanceCharges(charges); charges.forEach((_, index) => { setupAllowanceChargeListeners(index); addChargeVatTypeChangeListener(index); }); } function populateLineItems(xmlDoc) { const lineItems = xmlDoc.querySelectorAll('cac\\:InvoiceLine, InvoiceLine'); const lineItemsContainer = document.getElementById('lineItems'); lineItemsContainer.innerHTML = '

Articole Factură

'; lineItems.forEach((item, index) => { const quantity = getXMLValue(item, 'cbc\\:InvoicedQuantity, InvoicedQuantity', '0'); const unitCode = item.querySelector('cbc\\:InvoicedQuantity, InvoicedQuantity')?.getAttribute('unitCode') || 'EA'; const price = getXMLValue(item, 'cac\\:Price cbc\\:PriceAmount, Price PriceAmount', '0'); const itemElement = item.querySelector('cac\\:Item, Item'); const description = getXMLValue(itemElement, 'cbc\\:Name, Name', ''); const itemDescription = getXMLValue(itemElement, 'cbc\\:Description, Description', ''); const taxCategory = itemElement.querySelector('cac\\:ClassifiedTaxCategory, ClassifiedTaxCategory'); const vatType = getXMLValue(taxCategory, 'cbc\\:ID, ID') || 'S'; const vatRate = getXMLValue(taxCategory, 'cbc\\:Percent, Percent') || '19'; // Extragem discountul și codul de motiv de pe linie dacă există let lineDiscount = 0; let discountReasonCode = ''; const allowanceCharge = item.querySelector('cac\\:AllowanceCharge, AllowanceCharge'); if (allowanceCharge) { const chargeIndicator = getXMLValue(allowanceCharge, 'cbc\\:ChargeIndicator, ChargeIndicator'); if (chargeIndicator === 'false') { lineDiscount = parseFloat(getXMLValue(allowanceCharge, 'cbc\\:Amount, Amount', '0')); discountReasonCode = getXMLValue(allowanceCharge, 'cbc\\:AllowanceChargeReasonCode, AllowanceChargeReasonCode', ''); } } addUnitCode(unitCode); const lineItemHtml = createLineItemHTML( index, description, quantity, price, vatRate, unitCode, vatType, itemDescription, lineDiscount, discountReasonCode ); lineItemsContainer.insertAdjacentHTML('beforeend', lineItemHtml); // Parse identifications after adding the line item HTML if (itemElement) { parseIdentifications(itemElement, index); } if (!isVATRegistered()) { const vatTypeSelect = document.querySelector(`[name="vatType${index}"]`); const vatRateInput = document.querySelector(`[name="vatRate${index}"]`); if (vatTypeSelect && vatRateInput) { vatTypeSelect.value = 'O'; vatTypeSelect.disabled = true; vatRateInput.value = '0'; vatRateInput.disabled = true; } } // Enable/disable discount reason code field based on discount value const discountInput = document.querySelector(`[name="lineDiscount${index}"]`); const reasonCodeSelect = document.querySelector(`[name="discountReasonCode${index}"]`); if (discountInput && reasonCodeSelect) { reasonCodeSelect.disabled = lineDiscount <= 0; } handleLineItemChange(index); }); } function setupAllowanceChargeListeners(index) { const chargeAmountInput = document.querySelector(`[name="chargeAmount${index}"]`); const chargeTypeInput = document.querySelector(`[name="chargeType${index}"]`); const chargeVatTypeInput = document.querySelector(`[name="chargeVatType${index}"]`); const chargeVatRateInput = document.querySelector(`[name="chargeVatRate${index}"]`); const reasonCodeSelect = document.querySelector(`[name="chargeReasonCode${index}"]`); const reasonInput = document.querySelector(`[name="chargeReason${index}"]`); // Add change listeners to all inputs [chargeAmountInput, chargeTypeInput, chargeVatTypeInput, chargeVatRateInput].forEach(input => { if (input) { input.addEventListener('change', () => { manuallyEditedVatRows.clear(); refreshTotals(); }); } }); // Add reason code change listener if (reasonCodeSelect) { reasonCodeSelect.addEventListener('change', () => { const isCharge = chargeTypeInput.value === 'true'; const codes = isCharge ? CHARGE_REASON_CODES : ALLOWANCE_REASON_CODES; reasonInput.value = codes[reasonCodeSelect.value] || ''; }); } // Special handling for VAT type changes if (chargeVatTypeInput) { chargeVatTypeInput.addEventListener('change', () => handleChargeVatTypeChange(index)); } } function addChargeVatTypeChangeListener(index) { const vatTypeSelect = document.querySelector(`[name="chargeVatType${index}"]`); if (vatTypeSelect) { vatTypeSelect.addEventListener('change', function() { handleChargeVatTypeChange(index); // Force refresh of VAT breakdown displayVATBreakdown(); updateTotals(); }); } } function parseAllowanceCharges(xmlDoc) { const charges = []; // Folosim Set pentru a preveni dublarea elementelor din cauza namespace-urilor const processedIds = new Set(); // Selectăm toate elementele AllowanceCharge care sunt copii direcți ai Invoice const allowanceCharges = xmlDoc.querySelectorAll('cac\\:AllowanceCharge, AllowanceCharge'); allowanceCharges.forEach(ac => { // Verificăm dacă elementul este copil direct al Invoice și nu a fost deja procesat if (ac.parentElement === xmlDoc.documentElement) { const amount = getXMLValue(ac, 'cbc\\:Amount, Amount'); const reasonCode = getXMLValue(ac, 'cbc\\:AllowanceChargeReasonCode, AllowanceChargeReasonCode'); // Creăm un ID unic bazat pe cod și valoare pentru a evita duplicatele const uniqueId = `${reasonCode}-${amount}`; if (!processedIds.has(uniqueId)) { processedIds.add(uniqueId); const charge = { isCharge: getXMLValue(ac, 'cbc\\:ChargeIndicator, ChargeIndicator') === 'true', reasonCode: reasonCode, reason: getXMLValue(ac, 'cbc\\:AllowanceChargeReason, AllowanceChargeReason'), amount: parseFloat(amount) || 0, baseAmount: parseFloat(getXMLValue(ac, 'cbc\\:BaseAmount, BaseAmount')) || 0, vatRate: parseFloat(getXMLValue(ac, 'cac\\:TaxCategory cbc\\:Percent, TaxCategory Percent')) || 0, vatTypeId: getXMLValue(ac, 'cac\\:TaxCategory cbc\\:ID, TaxCategory ID', 'S'), multiplierFactorNumeric: parseFloat(getXMLValue(ac, 'cbc\\:MultiplierFactorNumeric, MultiplierFactorNumeric')) || 0 }; charges.push(charge); } } }); return charges; } function displayAllowanceCharges(charges) { const container = document.getElementById('allowanceCharges'); container.innerHTML = '

Reduceri și Taxe Suplimentare

'; charges.forEach((charge, index) => { const html = createAllowanceChargeHTML(index, charge); container.insertAdjacentHTML('beforeend', html); }); } function addAllowanceCharge() { const container = document.getElementById('allowanceCharges'); const index = document.querySelectorAll('.allowance-charge').length; // Create new charge with default values const newCharge = { isCharge: true, reasonCode: 'TV', reason: 'Cheltuieli transport', amount: 0, vatRate: 19.0, vatTypeId: 'S' }; // Add the HTML const html = createAllowanceChargeHTML(index, newCharge); container.insertAdjacentHTML('beforeend', html); // Setup event listeners setupAllowanceChargeListeners(index); // Force refresh of totals and VAT refreshTotals(); } function removeAllowanceCharge(index) { const charge = document.querySelector(`.allowance-charge[data-index="${index}"]`); if (charge) { // Remove the element charge.remove(); // Renumber remaining charges renumberAllowanceCharges(); // Clear manual edits and refresh totals manuallyEditedVatRows.clear(); refreshTotals(); } } function renumberAllowanceCharges() { document.querySelectorAll('.allowance-charge').forEach((charge, newIndex) => { // Update data-index charge.dataset.index = newIndex; // Update all input names charge.querySelectorAll('input, select').forEach(input => { const name = input.getAttribute('name'); if (name) { const baseName = name.replace(/\d+$/, ''); input.setAttribute('name', baseName + newIndex); } }); }); } function addLineItem() { const container = document.getElementById('lineItems'); const index = document.querySelectorAll('.line-item').length; // Determină tipul implicit de TVA bazat pe statusul furnizorului const defaultVatType = isVATRegistered() ? 'S' : 'O'; const defaultVatRate = isVATRegistered() ? '19' : '0'; const lineItemHtml = createLineItemHTML(index, '', '1', '0', defaultVatRate, 'EA', defaultVatType); container.insertAdjacentHTML('beforeend', lineItemHtml); const newItem = container.lastElementChild; const vatTypeSelect = newItem.querySelector(`[name="vatType${index}"]`); if (!isVATRegistered()) { vatTypeSelect.disabled = true; newItem.querySelector(`[name="vatRate${index}"]`).disabled = true; } // Adăugăm event handlers pentru noua linie handleLineItemChange(index); manuallyEditedVatRows.clear(); refreshTotals(); } function removeLineItem(index) { const lineItem = document.querySelector(`.line-item[data-index="${index}"]`); if (lineItem) { lineItem.remove(); renumberLineItems(); manuallyEditedVatRows.clear(); // Clear manual edits refreshTotals(); // Recalculate all totals } } function handleLineItemChange(index) { const quantityInput = document.querySelector(`[name="quantity${index}"]`); const priceInput = document.querySelector(`[name="price${index}"]`); const discountInput = document.querySelector(`[name="lineDiscount${index}"]`); const reasonCodeSelect = document.querySelector(`[name="discountReasonCode${index}"]`); // Handler pentru schimbarea discount-ului discountInput?.addEventListener('input', function() { const discountValue = parseFloat(this.value) || 0; if (reasonCodeSelect) { reasonCodeSelect.disabled = discountValue == 0; // activat pentru orice valoare nenulă // Dacă discount != 0 și nu e selectat niciun cod, selectăm codul implicit if (discountValue != 0 && !reasonCodeSelect.value) { reasonCodeSelect.value = '95'; // Cod implicit pentru reducere } } updateTotals(); }); // Handlers pentru cantitate și preț quantityInput?.addEventListener('change', updateTotals); priceInput?.addEventListener('change', updateTotals); // Force refresh updateTotals(); } function handleStorno() { document.querySelectorAll('.line-item').forEach((item, index) => { const quantityInput = document.querySelector(`[name="quantity${index}"]`); const currentValue = parseFloat(quantityInput.value); quantityInput.value = -currentValue; }); document.querySelectorAll('.allowance-charge').forEach((item, index) => { const amountInput = document.querySelector(`[name="chargeAmount${index}"]`); const currentAmount = parseFloat(amountInput.value); amountInput.value = -currentAmount; }); document.querySelectorAll('.vat-row').forEach(row => { const baseInput = row.querySelector('.vat-base'); const amountInput = row.querySelector('.vat-amount'); if (baseInput) { const currentBase = parseFloat(baseInput.value) || 0; baseInput.value = (-currentBase).toFixed(2); } if (amountInput) { const currentAmount = parseFloat(amountInput.value) || 0; amountInput.value = (-currentAmount).toFixed(2); } }); manuallyEditedVatRows.clear(); refreshTotals(); if (currentInvoice) { updateTaxTotals(currentInvoice); } } function updateTotals() { // Calculăm totalurile liniilor (deja nete, după discount) const lineItemTotals = calculateLineItemTotals(); const chargeTotals = calculateChargeTotals(); const subtotal = lineItemTotals.subtotal; const allowances = chargeTotals.allowances; const charges = chargeTotals.charges; const netAmount = subtotal - allowances + charges; // Calculate VAT breakdown const { vatBreakdown } = calculateVATBreakdown(); let totalVat = 0; vatBreakdown.forEach((entry) => { if (entry.type === 'S') { totalVat += entry.vatAmount; } }); // Display totals with 2 decimal places displayTotals({ subtotal: roundNumber(subtotal, 2), allowances: roundNumber(allowances, 2), charges: roundNumber(charges, 2), netAmount: roundNumber(netAmount, 2), totalVat: roundNumber(totalVat, 2), total: roundNumber(netAmount + totalVat, 2), vatBreakdown }); } function refreshTotals() { // Calculate line items first const lineItemTotals = calculateLineItemTotals(); const chargeTotals = calculateChargeTotals(); const subtotal = lineItemTotals.subtotal; const allowances = chargeTotals.allowances; const charges = chargeTotals.charges; const netAmount = subtotal - allowances + charges; // Calculate VAT breakdown const { vatBreakdown } = calculateVATBreakdown(); let totalVat = 0; if (vatBreakdown) { vatBreakdown.forEach(entry => { totalVat += entry.vatAmount; }); } // Display totals displayTotals({ subtotal, allowances, charges, netAmount, totalVat, total: netAmount + totalVat, vatBreakdown }); } function calculateLineItemTotals() { let subtotal = 0; document.querySelectorAll('.line-item').forEach((item, index) => { const quantity = parseFloat(document.querySelector(`[name="quantity${index}"]`).value) || 0; const price = parseFloat(document.querySelector(`[name="price${index}"]`).value) || 0; const lineDiscount = parseFloat(document.querySelector(`[name="lineDiscount${index}"]`).value) || 0; // LineExtensionAmount = cantitate * preț - discount const lineAmount = (quantity * price) - lineDiscount; subtotal += lineAmount; }); return { subtotal: roundNumber(subtotal, 2) }; } function calculateChargeTotals() { let allowances = 0; let charges = 0; document.querySelectorAll('.allowance-charge').forEach((item, index) => { const isCharge = document.querySelector(`[name="chargeType${index}"]`).value === 'true'; const amount = parseFloat(document.querySelector(`[name="chargeAmount${index}"]`).value) || 0; if (isCharge) { charges += amount; } else { allowances += amount; } }); return { allowances: roundNumber(allowances, 2), charges: roundNumber(charges, 2) }; } function calculateTotals() { let subtotal = 0; let vatBreakdown = new Map(); // Calculăm totalurile liniilor document.querySelectorAll('.line-item').forEach((item, index) => { const quantity = parseFloat(document.querySelector(`[name="quantity${index}"]`).value) || 0; const price = parseFloat(document.querySelector(`[name="price${index}"]`).value) || 0; const lineDiscount = parseFloat(document.querySelector(`[name="lineDiscount${index}"]`).value) || 0; const vatType = document.querySelector(`[name="vatType${index}"]`).value; const vatRate = parseFloat(document.querySelector(`[name="vatRate${index}"]`).value) || 0; // LineExtensionAmount pentru linie = (cantitate * preț) - discount linie const lineTotal = (quantity * price) - lineDiscount; subtotal += lineTotal; // Actualizăm baza de TVA pentru această linie const key = `${vatRate}-${vatType}`; if (!vatBreakdown.has(key)) { vatBreakdown.set(key, { baseAmount: 0, vatAmount: 0, rate: vatRate, type: vatType }); } const entry = vatBreakdown.get(key); // Adăugăm la baza de TVA valoarea netă a liniei (după discount) entry.baseAmount += lineTotal; }); // Calculăm discounturile și taxele globale const { allowances, charges } = calculateChargeTotals(); // Calculăm suma netă după aplicarea discount-urilor și taxelor globale const netAmount = subtotal - allowances + charges; // Pentru fiecare discount/taxă globală, ajustăm baza de TVA corespunzătoare document.querySelectorAll('.allowance-charge').forEach((item, index) => { const amount = parseFloat(document.querySelector(`[name="chargeAmount${index}"]`).value) || 0; const vatType = document.querySelector(`[name="chargeVatType${index}"]`).value; const vatRate = parseFloat(document.querySelector(`[name="chargeVatRate${index}"]`).value) || 0; const isCharge = document.querySelector(`[name="chargeType${index}"]`).value === 'true'; const key = `${vatRate}-${vatType}`; if (!vatBreakdown.has(key)) { vatBreakdown.set(key, { baseAmount: 0, vatAmount: 0, rate: vatRate, type: vatType }); } const entry = vatBreakdown.get(key); // Ajustăm baza de TVA în funcție de tipul operațiunii (discount/taxă) const adjustmentAmount = isCharge ? amount : -amount; entry.baseAmount += adjustmentAmount; }); // Calculăm TVA-ul pentru fiecare rată let totalVat = 0; vatBreakdown.forEach((entry, key) => { if (entry.type === 'S') { entry.vatAmount = roundNumber(entry.baseAmount * entry.rate / 100, 2); totalVat += entry.vatAmount; } }); return { subtotal: roundNumber(subtotal), allowances: roundNumber(allowances), charges: roundNumber(charges), netAmount: roundNumber(netAmount), totalVat: roundNumber(totalVat), total: roundNumber(netAmount + totalVat), vatBreakdown }; } function calculateVATBases(lines, globalAllowances, globalCharges) { // Initialize VAT bases const vatBases = {}; // First pass: calculate base amounts per VAT rate from lines lines.forEach(line => { const vatRate = line.vatRate; if (!vatBases[vatRate]) { vatBases[vatRate] = { base: 0, allowances: 0, charges: 0 }; } const lineTotal = line.quantity * line.price; const lineAfterDiscount = lineTotal - line.lineDiscount; vatBases[vatRate].base += lineAfterDiscount; }); // Second pass: apply global allowances and charges per VAT rate globalAllowances.forEach(allowance => { const vatRate = allowance.vatRate; if (vatBases[vatRate]) { vatBases[vatRate].allowances += allowance.amount; } }); globalCharges.forEach(charge => { const vatRate = charge.vatRate; if (vatBases[vatRate]) { vatBases[vatRate].charges += charge.amount; } }); // Calculate final VAT amounts Object.keys(vatBases).forEach(rate => { const rateData = vatBases[rate]; const netBase = rateData.base - rateData.allowances + rateData.charges; rateData.vatAmount = netBase * (parseFloat(rate) / 100); }); return vatBases; } function calculateTotalVAT() { let totalVat = Array.from(document.querySelectorAll('.vat-amount')) .reduce((sum, input) => sum + formatter.parseCurrency(input.value), 0); return roundNumber(totalVat, 2); } function calculateVATBreakdown() { let vatBreakdown = new Map(); let totalVat = 0; // Process line items document.querySelectorAll('.line-item').forEach((item, index) => { const quantity = parseFloat(document.querySelector(`[name="quantity${index}"]`).value) || 0; const price = parseFloat(document.querySelector(`[name="price${index}"]`).value) || 0; const lineDiscount = parseFloat(document.querySelector(`[name="lineDiscount${index}"]`).value) || 0; const vatType = document.querySelector(`[name="vatType${index}"]`).value; const vatRate = parseFloat(document.querySelector(`[name="vatRate${index}"]`).value) || 0; const lineAmount = (quantity * price) - lineDiscount; const key = `${vatRate}-${vatType}`; if (!vatBreakdown.has(key)) { vatBreakdown.set(key, { baseAmount: 0, vatAmount: 0, rate: vatRate, type: vatType }); } const entry = vatBreakdown.get(key); entry.baseAmount += lineAmount; }); // Process allowances and charges document.querySelectorAll('.allowance-charge').forEach((charge, index) => { const amount = parseFloat(document.querySelector(`[name="chargeAmount${index}"]`).value) || 0; const vatType = document.querySelector(`[name="chargeVatType${index}"]`).value; const vatRate = parseFloat(document.querySelector(`[name="chargeVatRate${index}"]`).value) || 0; const isCharge = document.querySelector(`[name="chargeType${index}"]`).value === 'true'; if (amount === 0) return; const key = `${vatRate}-${vatType}`; if (!vatBreakdown.has(key)) { vatBreakdown.set(key, { baseAmount: 0, vatAmount: 0, rate: vatRate, type: vatType }); } const entry = vatBreakdown.get(key); const adjustedAmount = isCharge ? amount : -amount; entry.baseAmount += adjustedAmount; }); // Calculate VAT for each rate vatBreakdown.forEach((entry) => { if (entry.type === 'S') { entry.vatAmount = roundNumber(entry.baseAmount * entry.rate / 100, 2); totalVat += entry.vatAmount; } }); return { vatBreakdown, totalVat: roundNumber(totalVat, 2) }; } window.updateVATRow = function(rowId, source) { const row = document.getElementById(rowId); if (!row) return; const typeSelect = row.querySelector('.vat-type'); const rateInput = row.querySelector('.vat-rate'); const baseInput = row.querySelector('.vat-base'); const amountInput = row.querySelector('.vat-amount'); if (source === 'manual') { manuallyEditedVatRows.add(rowId); // Keep existing values, just update totals updateTotalVAT(); refreshTotals(); return; } // Only calculate VAT amount for non-manual updates if (!manuallyEditedVatRows.has(rowId)) { const type = typeSelect.value; const rate = parseFloat(rateInput.value) || 0; const base = formatter.parseCurrency(baseInput.value) || 0; const calculatedAmount = type === 'S' ? roundNumber(base * rate / 100, 2) : 0; amountInput.value = formatter.formatCurrency(calculatedAmount); updateTotalVAT(); refreshTotals(); } }; window.updateVATRowFromAmount = function(rowId) { const row = document.getElementById(rowId); if (!row) return; // Just mark as manually edited and update the totals // Do not recalculate base amount manuallyEditedVatRows.add(rowId); const amountInput = row.querySelector('.vat-amount'); if (amountInput) { const value = formatter.parseCurrency(amountInput.value); amountInput.value = formatter.formatCurrency(value); } let totalVat = 0; document.querySelectorAll('.vat-row').forEach(vatRow => { const vatAmount = formatter.parseCurrency(vatRow.querySelector('.vat-amount').value) || 0; totalVat += vatAmount; }); // Update just total VAT and final total const netAmount = formatter.parseCurrency(document.getElementById('netAmount').textContent); document.getElementById('vat').textContent = formatter.formatCurrency(totalVat); document.getElementById('total').textContent = formatter.formatCurrency(netAmount + totalVat); }; window.removeVATRow = function(rowId) { const row = document.getElementById(rowId); if (row) { manuallyEditedVatRows.delete(rowId); row.remove(); updateTotalVAT(); refreshTotals(); } }; window.addVATRate = function() { const container = document.getElementById('vatBreakdownRows'); addVATBreakdownRow(19, 0, 0); refreshTotals(); }; function updateTotalVAT() { const totalVat = Array.from(document.querySelectorAll('.vat-amount')) .reduce((sum, input) => sum + (parseFloat(input.value) || 0), 0); document.getElementById('vat').textContent = totalVat.toFixed(2); const netAmount = parseFloat(document.getElementById('netAmount').textContent) || 0; const total = netAmount + totalVat; document.getElementById('total').textContent = total.toFixed(2); } function updateVATBreakdown() { // Șterge și reconstruiește rândurile TVA const container = document.getElementById('vatBreakdownRows'); if (!container) return; container.innerHTML = ''; const { vatBreakdown } = calculateVATBreakdown(); vatBreakdown.forEach((data, key) => { const [rate, type] = key.split('-'); addVATBreakdownRow( parseFloat(rate), data.baseAmount, data.vatAmount, type ); }); } function displayVATBreakdown(xmlDoc = null) { const container = document.getElementById('vatBreakdownRows'); if (!container) return; // Clear container container.innerHTML = ''; // If XML is provided, use its VAT breakdown if (xmlDoc && originalTotals && originalTotals.vatBreakdown) { originalTotals.vatBreakdown.forEach((vat, index) => { addVATBreakdownRow( vat.percent, vat.taxableAmount, vat.taxAmount, vat.type, `vat-row-${index}`, vat.exemptionCode, vat.exemptionReason ); }); } else { // Calculate current VAT breakdown const { vatBreakdown } = calculateVATBreakdown(); vatBreakdown.forEach((data, key) => { const [rate, type] = key.split('-'); addVATBreakdownRow( parseFloat(rate), data.baseAmount, data.vatAmount, type ); }); } } function createEmptyInvoice() { const parser = new DOMParser(); const xmlString = ` urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.1 380 RON RO RO VAT RO VAT `; return parser.parseFromString(xmlString, "text/xml"); } function saveXML() { if (!validateForm()) return; try { if (!currentInvoice) { currentInvoice = createEmptyInvoice(); } const xmlDoc = currentInvoice; // Update all the data updateBasicDetails(xmlDoc); updatePartyDetails(xmlDoc); updateAllowanceCharges(xmlDoc); // Remove existing TaxTotal and LegalMonetaryTotal elements const existingTaxTotals = xmlDoc.querySelectorAll('cac\\:TaxTotal, TaxTotal'); existingTaxTotals.forEach(el => el.remove()); const existingMonetaryTotal = xmlDoc.querySelector('cac\\:LegalMonetaryTotal, LegalMonetaryTotal'); if (existingMonetaryTotal) { existingMonetaryTotal.remove(); } // Remove existing InvoiceLine elements const existingLines = xmlDoc.querySelectorAll('cac\\:InvoiceLine, InvoiceLine'); existingLines.forEach(el => el.remove()); // Add elements in the correct order updateTaxTotals(xmlDoc); updateMonetaryTotals(xmlDoc); updateLineItems(xmlDoc); downloadXML(xmlDoc); } catch (error) { handleError(error, 'Eroare la salvarea fișierului XML'); } } function updateBasicDetails(xmlDoc) { setXMLValue(xmlDoc, 'cbc\\:ID, ID', document.querySelector('[name="invoiceNumber"]').value); // Remove existing Note elements const existingNotes = xmlDoc.querySelectorAll('cbc\\:Note, Note'); existingNotes.forEach(note => note.remove()); // Split note text and create new Note elements const noteText = document.querySelector('[name="invoiceNote"]').value; if (noteText) { const insertAfter = xmlDoc.querySelector('cbc\\:InvoiceTypeCode, InvoiceTypeCode'); const chunks = splitNoteIntoChunks(noteText, 300); chunks.forEach(chunk => { const noteElement = createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:Note", chunk); if (insertAfter && insertAfter.parentNode) { insertAfter.parentNode.insertBefore(noteElement, insertAfter.nextSibling); } }); } const issueDateValue = document.querySelector('[name="issueDate"]').value; const dueDateValue = document.querySelector('[name="dueDate"]').value; setXMLValue(xmlDoc, 'cbc\\:IssueDate, IssueDate', parseRomanianDate(issueDateValue)); setXMLValue(xmlDoc, 'cbc\\:DueDate, DueDate', parseRomanianDate(dueDateValue)); // Update currency codes const documentCurrencyCode = document.querySelector('[name="documentCurrencyCode"]').value.toUpperCase() || 'RON'; setXMLValue(xmlDoc, 'cbc\\:DocumentCurrencyCode, DocumentCurrencyCode', documentCurrencyCode); const taxCurrencyCode = document.querySelector('[name="taxCurrencyCode"]').value.toUpperCase(); if (taxCurrencyCode) { let taxCurrencyElement = xmlDoc.querySelector('cbc\\:TaxCurrencyCode, TaxCurrencyCode'); if (!taxCurrencyElement) { taxCurrencyElement = createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:TaxCurrencyCode"); const insertAfter = xmlDoc.querySelector('cbc\\:DocumentCurrencyCode, DocumentCurrencyCode'); if (insertAfter && insertAfter.parentNode) { insertAfter.parentNode.insertBefore(taxCurrencyElement, insertAfter.nextSibling); } } taxCurrencyElement.textContent = taxCurrencyCode; } else { // Remove TaxCurrencyCode if it exists and is empty const taxCurrencyElement = xmlDoc.querySelector('cbc\\:TaxCurrencyCode, TaxCurrencyCode'); if (taxCurrencyElement) { taxCurrencyElement.parentNode.removeChild(taxCurrencyElement); } } } function createPartyElement(xmlDoc, isSupplier, partyData) { const party = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:Party"); const hasVatPrefix = /^[A-Z]{2}/.test(partyData.vat?.trim() || ''); const IsNeplatitor = vatHasO(); // Add PartyIdentification if (partyData.companyId) { const partyIdentification = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:PartyIdentification"); partyIdentification.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ID", partyData.companyId)); party.appendChild(partyIdentification); } function validateCountryCode(countryCode) { const code = countryCode?.trim().toUpperCase() || 'RO'; return ISO_3166_1_CODES.has(code) ? code : 'RO'; } function validateCountyCode(countryCode, countyCode) { if (countryCode === 'RO') { return ROMANIAN_COUNTY_CODES.has(countyCode) ? countyCode : 'RO-B'; } return countyCode; } const postalAddress = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:PostalAddress"); postalAddress.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:StreetName", partyData.address)); postalAddress.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:CityName", partyData.city)); postalAddress.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:CountrySubentity", partyData.county)); const country = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:Country"); // Ensure country code is valid ISO 3166-1 format (2 uppercase letters) const countryCode = partyData.country?.trim().toUpperCase() || 'RO'; country.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:IdentificationCode", countryCode)); postalAddress.appendChild(country); party.appendChild(postalAddress); const vatTypesInUse = new Set(); document.querySelectorAll('.line-item').forEach((item, index) => { const vatType = document.querySelector(`[name="vatType${index}"]`).value; vatTypesInUse.add(vatType); }); // Add PartyTaxScheme for all suppliers VAT types except 'O' or all customers with VAT prefix if (hasVatPrefix ) { const partyTaxScheme = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:PartyTaxScheme"); partyTaxScheme.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:CompanyID", partyData.vat.toUpperCase())); const taxScheme = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:TaxScheme"); if (!IsNeplatitor) { taxScheme.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ID", "VAT")); } else { // console.log('not adding VAT ID'); } partyTaxScheme.appendChild(taxScheme); party.appendChild(partyTaxScheme); } // Funcție pentru detectarea dacă există un VAT cu tipul 'O' Neplatitor de TVA // În cazul în care există, nu se va adăuga un element TaxScheme cu ID-ul 'VAT' function vatHasO() { const vatTypesInUse = new Set(); document.querySelectorAll('.line-item').forEach((item, index) => { const vatType = document.querySelector(`[name="vatType${index}"]`).value; vatTypesInUse.add(vatType); }); return vatTypesInUse.size > 0 && vatTypesInUse.has('O'); } // Add PartyLegalEntity const partyLegalEntity = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:PartyLegalEntity"); partyLegalEntity.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:RegistrationName", partyData.name)); // Add CompanyID if (!hasVatPrefix) { // For non-VAT registered supplier, use VAT number in CompanyID partyLegalEntity.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:CompanyID", partyData.vat)); } else { // For others, use companyId partyLegalEntity.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:CompanyID", partyData.companyId)); } party.appendChild(partyLegalEntity); // Add Contact if phone, email or contactName exists if (partyData.phone || partyData.email || partyData.contactName) { const contact = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:Contact"); if (partyData.contactName) { contact.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:Name", partyData.contactName)); } if (partyData.phone) { contact.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:Telephone", partyData.phone)); } if (partyData.email) { contact.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ElectronicMail", partyData.email)); } party.appendChild(contact); } return party; } function updatePartyDetails(xmlDoc) { // Update supplier details const supplierData = { name: document.querySelector('[name="supplierName"]').value, vat: document.querySelector('[name="supplierVAT"]').value, companyId: document.querySelector('[name="supplierCompanyId"]').value, address: document.querySelector('[name="supplierAddress"]').value, city: document.querySelector('[name="supplierCity"]').value, county: document.querySelector('[name="supplierCountrySubentity"]').value, country: document.querySelector('[name="supplierCountry"]').value, phone: document.querySelector('[name="supplierPhone"]').value, contactName: document.querySelector('[name="supplierContactName"]').value, email: document.querySelector('[name="supplierEmail"]').value }; updatePartyXML(xmlDoc, true, supplierData); // Update customer details const customerData = { name: document.querySelector('[name="customerName"]').value, vat: document.querySelector('[name="customerVAT"]').value, companyId: document.querySelector('[name="customerCompanyId"]').value, address: document.querySelector('[name="customerAddress"]').value, city: document.querySelector('[name="customerCity"]').value, county: document.querySelector('[name="customerCountrySubentity"]').value, country: document.querySelector('[name="customerCountry"]').value, phone: document.querySelector('[name="customerPhone"]').value, contactName: document.querySelector('[name="customerContactName"]').value, email: document.querySelector('[name="customerEmail"]').value }; updatePartyXML(xmlDoc, false, customerData); } function updatePartyXML(xmlDoc, isSupplier, partyData) { const partyElement = createPartyElement(xmlDoc, isSupplier, partyData); const parentTag = isSupplier ? 'AccountingSupplierParty' : 'AccountingCustomerParty'; let parentElement = xmlDoc.querySelector(`cac\\:${parentTag}, ${parentTag}`); if (!parentElement) { parentElement = createXMLElement(xmlDoc, XML_NAMESPACES.cac, `cac:${parentTag}`); const insertPoint = isSupplier ? xmlDoc.querySelector('cbc\\:DocumentCurrencyCode, DocumentCurrencyCode') : xmlDoc.querySelector('cac\\:AccountingSupplierParty, AccountingSupplierParty'); if (insertPoint && insertPoint.parentNode) { insertPoint.parentNode.insertBefore(parentElement, insertPoint.nextSibling); } else { xmlDoc.documentElement.appendChild(parentElement); } } // Remove existing Party element if it exists const existingParty = parentElement.querySelector('cac\\:Party, Party'); if (existingParty) { existingParty.remove(); } parentElement.appendChild(partyElement); } function getAllowanceCharges() { const charges = []; document.querySelectorAll('.allowance-charge').forEach((item, index) => { charges.push({ isCharge: document.querySelector(`[name="chargeType${index}"]`).value === 'true', reasonCode: document.querySelector(`[name="chargeReasonCode${index}"]`).value, reason: document.querySelector(`[name="chargeReason${index}"]`).value, amount: parseFloat(document.querySelector(`[name="chargeAmount${index}"]`).value) || 0, vatRate: parseFloat(document.querySelector(`[name="chargeVatRate${index}"]`).value) || 19.0, vatTypeId: document.querySelector(`[name="chargeVatType${index}"]`).value || 'S' }); }); return charges; } function updateAllowanceCharges(xmlDoc) { const currencyID = document.querySelector('[name="documentCurrencyCode"]').value.toUpperCase() || 'RON'; // Remove existing global allowances/charges const existingCharges = xmlDoc.querySelectorAll('Invoice > cac\\:AllowanceCharge, Invoice > AllowanceCharge'); existingCharges.forEach(charge => charge.remove()); // Add global allowances document.querySelectorAll('.allowance-charge').forEach((item, index) => { const allowanceElement = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:AllowanceCharge"); const isCharge = document.querySelector(`[name="chargeType${index}"]`).value === 'true'; const amount = parseFloat(document.querySelector(`[name="chargeAmount${index}"]`).value) || 0; const baseAmount = parseFloat(document.querySelector(`[name="chargeBaseAmount${index}"]`).value) || 0; const reasonCode = document.querySelector(`[name="chargeReasonCode${index}"]`).value; const reason = document.querySelector(`[name="chargeReason${index}"]`).value; const vatTypeId = document.querySelector(`[name="chargeVatType${index}"]`).value; const vatRate = parseFloat(document.querySelector(`[name="chargeVatRate${index}"]`).value) || 0; allowanceElement.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ChargeIndicator", isCharge.toString())); if (reasonCode) { allowanceElement.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:AllowanceChargeReasonCode", reasonCode)); } if (reason) { allowanceElement.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:AllowanceChargeReason", reason)); } // Add multiplier if exists if (baseAmount > 0) { const multiplier = (amount / baseAmount) * 100; allowanceElement.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:MultiplierFactorNumeric", multiplier.toFixed(2))); } allowanceElement.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:Amount", amount.toFixed(2), { currencyID })); if (baseAmount > 0) { allowanceElement.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:BaseAmount", baseAmount.toFixed(2), { currencyID })); } // Add tax category const taxCategory = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:TaxCategory"); taxCategory.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ID", vatTypeId)); taxCategory.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:Percent", vatRate.toString())); const taxScheme = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:TaxScheme"); taxScheme.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ID", "VAT")); taxCategory.appendChild(taxScheme); allowanceElement.appendChild(taxCategory); // Insert before TaxTotal const taxTotal = xmlDoc.querySelector('cac\\:TaxTotal, TaxTotal'); if (taxTotal) { xmlDoc.documentElement.insertBefore(allowanceElement, taxTotal); } else { xmlDoc.documentElement.appendChild(allowanceElement); } }); } function updateLineItems(xmlDoc) { const currencyID = xmlDoc.querySelector('cbc\\:DocumentCurrencyCode, DocumentCurrencyCode').textContent; // Șterge liniile existente const existingLines = xmlDoc.querySelectorAll('cac\\:InvoiceLine, InvoiceLine'); existingLines.forEach(line => line.remove()); document.querySelectorAll('.line-item').forEach((item, index) => { const invoiceLine = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:InvoiceLine"); // Obține valorile liniei const quantity = document.querySelector(`[name="quantity${index}"]`)?.value || '0'; const unitCode = document.querySelector(`[name="unit${index}"]`)?.value || 'EA'; const price = document.querySelector(`[name="price${index}"]`)?.value || '0'; const description = document.querySelector(`[name="description${index}"]`)?.value || ''; const itemDescription = document.querySelector(`[name="itemDescription${index}"]`)?.value || ''; const vatType = document.querySelector(`[name="vatType${index}"]`)?.value || 'S'; const vatRate = vatType === 'AE' ? '0.00' : (document.querySelector(`[name="vatRate${index}"]`)?.value || '0'); const lineDiscount = parseFloat(document.querySelector(`[name="lineDiscount${index}"]`)?.value) || 0; // Calculează valorile liniei const baseLineAmount = quantity * price; const lineAmount = roundNumber(baseLineAmount - lineDiscount); // Adaugă elementele liniei invoiceLine.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ID", (index + 1).toString())); const quantityElement = createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:InvoicedQuantity", quantity); quantityElement.setAttribute('unitCode', unitCode); invoiceLine.appendChild(quantityElement); // Adaugă LineExtensionAmount (valoarea după discount) invoiceLine.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:LineExtensionAmount", lineAmount.toFixed(2), { currencyID })); // Adaugă discount pe linie dacă există if (lineDiscount != 0) { const allowanceCharge = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:AllowanceCharge"); allowanceCharge.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ChargeIndicator", lineDiscount < 0 ? "true" : "false")); const reasonCode = document.querySelector(`[name="discountReasonCode${index}"]`)?.value || '95'; allowanceCharge.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:AllowanceChargeReasonCode", reasonCode)); allowanceCharge.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:AllowanceChargeReason", ALLOWANCE_REASON_CODES[reasonCode] || 'Reducere')); // BaseAmount trebuie să fie valoarea netă înainte de discount const baseAmount = Math.abs(lineAmount); const discountAmount = Math.abs(lineDiscount); const multiplierFactor = (discountAmount / baseAmount * 100).toFixed(2); allowanceCharge.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:MultiplierFactorNumeric", multiplierFactor)); allowanceCharge.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:Amount", discountAmount.toFixed(2), { currencyID })); allowanceCharge.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:BaseAmount", baseAmount.toFixed(2), { currencyID })); invoiceLine.appendChild(allowanceCharge); } // Adaugă detaliile articolului const itemElement = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:Item"); if (itemDescription) { itemElement.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:Description", itemDescription)); } itemElement.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:Name", description)); // Adaugă identificările const identificationsContainer = document.querySelector(`#identifications${index}`); if (identificationsContainer) { saveIdentificationsToXML(xmlDoc, itemElement, index); } // Adaugă categoria de taxă const taxCategory = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:ClassifiedTaxCategory"); taxCategory.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ID", vatType)); if (vatType !== 'O') { taxCategory.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:Percent", vatRate)); } const taxScheme = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:TaxScheme"); taxScheme.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ID", "VAT")); taxCategory.appendChild(taxScheme); itemElement.appendChild(taxCategory); invoiceLine.appendChild(itemElement); // Adaugă prețul const priceElement = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:Price"); priceElement.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:PriceAmount", price, { currencyID })); invoiceLine.appendChild(priceElement); xmlDoc.documentElement.appendChild(invoiceLine); }); } function updateTaxTotals(xmlDoc) { const currencyID = document.querySelector('[name="documentCurrencyCode"]').value.toUpperCase() || 'RON'; const taxCurrencyCode = document.querySelector('[name="taxCurrencyCode"]').value.toUpperCase(); // Remove existing TaxTotal elements const existingTaxTotals = xmlDoc.querySelectorAll('cac\\:TaxTotal, TaxTotal'); existingTaxTotals.forEach(element => element.parentNode.removeChild(element)); // Create main TaxTotal for document currency const taxTotal = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:TaxTotal"); // Use precise currency parsing for total VAT const uiTotalVat = formatter.parseCurrency(document.getElementById('vat').textContent); const taxAmountElement = createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:TaxAmount", uiTotalVat.toFixed(2), { currencyID }); taxTotal.appendChild(taxAmountElement); // Add TaxSubtotal elements to the main TaxTotal const vatRows = document.querySelectorAll('.vat-row'); vatRows.forEach(row => { const taxSubtotal = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:TaxSubtotal"); const baseAmount = formatter.parseCurrency(row.querySelector('.vat-base').value) || 0; const vatAmount = formatter.parseCurrency(row.querySelector('.vat-amount').value) || 0; const vatType = row.querySelector('.vat-type').value; const vatRate = parseFloat(row.querySelector('.vat-rate').value) || 0; taxSubtotal.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:TaxableAmount", baseAmount.toFixed(2), { currencyID })); taxSubtotal.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:TaxAmount", vatAmount.toFixed(2), { currencyID })); const taxCategory = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:TaxCategory"); taxCategory.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ID", vatType)); const percent = vatType === 'AE' ? '0.00' : vatRate.toFixed(2); taxCategory.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:Percent", percent)); // Add exemption code and reason for special VAT types if (['E', 'K', 'O', 'AE'].includes(vatType)) { const exemptionCode = row.querySelector('.vat-exemption-code')?.value; const exemptionReason = row.querySelector('.vat-exemption-reason')?.value; if (exemptionCode) { taxCategory.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:TaxExemptionReasonCode", exemptionCode)); } if (exemptionReason) { taxCategory.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:TaxExemptionReason", exemptionReason)); } } const taxScheme = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:TaxScheme"); taxScheme.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:ID", "VAT")); taxCategory.appendChild(taxScheme); taxSubtotal.appendChild(taxCategory); taxTotal.appendChild(taxSubtotal); }); // Insert TaxTotal in the correct position let insertionPoint = xmlDoc.querySelector('cac\\:AllowanceCharge, AllowanceCharge'); if (insertionPoint) { while (insertionPoint.nextElementSibling && (insertionPoint.nextElementSibling.localName === 'AllowanceCharge' || insertionPoint.nextElementSibling.localName === 'TaxTotal')) { insertionPoint = insertionPoint.nextElementSibling; } insertionPoint.parentNode.insertBefore(taxTotal, insertionPoint.nextSibling); } else { const monetaryTotal = xmlDoc.querySelector('cac\\:LegalMonetaryTotal, LegalMonetaryTotal'); if (monetaryTotal) { monetaryTotal.parentNode.insertBefore(taxTotal, monetaryTotal); } else { xmlDoc.documentElement.appendChild(taxTotal); } } // If tax currency is specified, add another TaxTotal element if (taxCurrencyCode && taxCurrencyCode !== currencyID) { const taxCurrencyTotal = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:TaxTotal"); const exchangeRateInput = document.querySelector('[name="exchangeRate"]'); const exchangeRate = exchangeRateInput ? parseFloat(exchangeRateInput.value) || 1 : 1; const taxCurrencyVAT = uiTotalVat * exchangeRate; const taxCurrencyAmountElement = createXMLElement(xmlDoc, XML_NAMESPACES.cbc, "cbc:TaxAmount", taxCurrencyVAT.toFixed(2), { currencyID: taxCurrencyCode }); taxCurrencyTotal.appendChild(taxCurrencyAmountElement); // Insert after the main TaxTotal taxTotal.parentNode.insertBefore(taxCurrencyTotal, taxTotal.nextSibling); } } function updateMonetaryTotals(xmlDoc) { const currencyID = document.querySelector('[name="documentCurrencyCode"]').value.toUpperCase() || 'RON'; // Use direct parsing to preserve full precision and manual edits const subtotal = formatter.parseCurrency(document.getElementById('subtotal').textContent); const allowances = formatter.parseCurrency(document.getElementById('totalAllowances').textContent); const charges = formatter.parseCurrency(document.getElementById('totalCharges').textContent); const netAmount = formatter.parseCurrency(document.getElementById('netAmount').textContent); const totalVat = formatter.parseCurrency(document.getElementById('vat').textContent); const total = formatter.parseCurrency(document.getElementById('total').textContent); // Rest of the function remains the same... const existingMonetaryTotal = xmlDoc.querySelector('cac\\:LegalMonetaryTotal, LegalMonetaryTotal'); if (existingMonetaryTotal) { existingMonetaryTotal.parentNode.removeChild(existingMonetaryTotal); } const monetaryTotal = createXMLElement(xmlDoc, XML_NAMESPACES.cac, "cac:LegalMonetaryTotal"); const amounts = { "LineExtensionAmount": subtotal, "TaxExclusiveAmount": netAmount, "TaxInclusiveAmount": total, "AllowanceTotalAmount": allowances, "ChargeTotalAmount": charges, "PayableAmount": total }; Object.entries(amounts).forEach(([elementName, value]) => { monetaryTotal.appendChild(createXMLElement(xmlDoc, XML_NAMESPACES.cbc, `cbc:${elementName}`, value.toFixed(2), { currencyID })); }); // Insertion logic remains the same let insertionPoint = xmlDoc.querySelector('cac\\:TaxTotal, TaxTotal'); if (insertionPoint) { while (insertionPoint.nextElementSibling && insertionPoint.nextElementSibling.localName === 'TaxTotal') { insertionPoint = insertionPoint.nextElementSibling; } insertionPoint.parentNode.insertBefore(monetaryTotal, insertionPoint.nextSibling); } else { const firstInvoiceLine = xmlDoc.querySelector('cac\\:InvoiceLine, InvoiceLine'); if (firstInvoiceLine) { firstInvoiceLine.parentNode.insertBefore(monetaryTotal, firstInvoiceLine); } else { xmlDoc.documentElement.appendChild(monetaryTotal); } } } function getExchangeRate(fromCurrency, toCurrency) { // For now, return 1 as default exchange rate // TODO: Implement proper exchange rate handling return 1; } // Update the invoice form to include exchange rate when tax currency is different function addExchangeRateField() { const taxCurrencyInput = document.querySelector('[name="taxCurrencyCode"]'); const documentCurrencyInput = document.querySelector('[name="documentCurrencyCode"]'); function updateExchangeRateVisibility() { const taxCurrency = taxCurrencyInput.value.toUpperCase(); const documentCurrency = documentCurrencyInput.value.toUpperCase(); let exchangeRateContainer = document.getElementById('exchangeRateContainer'); if (taxCurrency && taxCurrency !== documentCurrency) { if (!exchangeRateContainer) { const container = document.createElement('div'); container.id = 'exchangeRateContainer'; container.className = 'form-group'; container.innerHTML = ` `; taxCurrencyInput.parentNode.after(container); } else { // Update label if currencies changed const label = exchangeRateContainer.querySelector('label'); label.textContent = `Curs Valutar ${documentCurrency}/${taxCurrency}`; } } else if (exchangeRateContainer) { exchangeRateContainer.remove(); } } taxCurrencyInput.addEventListener('input', updateExchangeRateVisibility); documentCurrencyInput.addEventListener('input', updateExchangeRateVisibility); // Initial check updateExchangeRateVisibility(); } // Utility functions function getXMLValue(xmlDoc, selector, defaultValue = '') { if (!xmlDoc) return defaultValue; try { const element = xmlDoc.querySelector(selector); const value = element ? element.textContent : defaultValue; // console.log(`getXMLValue: Selector: ${selector}, Value: ${value}`); return value; } catch (error) { console.warn(`Eroare la obținerea valorii pentru selectorul ${selector}:`, error); return defaultValue; } } function setXMLValue(xmlDoc, selector, value) { try { const element = xmlDoc.querySelector(selector); if (element) { element.textContent = value; return true; } return false; } catch (error) { console.warn(`Eroare la setarea valorii pentru selectorul ${selector}:`, error); return false; } } function createXMLElement(xmlDoc, namespace, elementName, value = '', attributes = {}) { const element = xmlDoc.createElementNS(namespace, elementName); if (value) { element.textContent = value; } Object.entries(attributes).forEach(([key, value]) => { element.setAttribute(key, value); }); return element; } function formatXML(xmlString) { let formatted = ''; let indent = ''; const tab = ' '; xmlString.split(/>\s* { if (node.match(/^\/\w/)) { indent = indent.substring(tab.length); } formatted += indent + '<' + node + '>\r\n'; if (node.match(/^]*[^\/]$/) && !node.startsWith("?")) { indent += tab; } }); return formatted.substring(1, formatted.length - 3); } function downloadXML(xmlDoc) { const serializer = new XMLSerializer(); let xmlString = serializer.serializeToString(xmlDoc); if (!xmlString.startsWith('\n' + xmlString; } xmlString = formatXML(xmlString); const blob = new Blob([xmlString], { type: 'application/xml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'factura_' + document.querySelector('[name="invoiceNumber"]').value + '.xml'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function roundNumber(number, decimals = 2) { return Math.round(number * Math.pow(10, decimals)) / Math.pow(10, decimals); } function renumberLineItems() { document.querySelectorAll('.line-item').forEach((item, newIndex) => { item.dataset.index = newIndex; item.querySelectorAll('input, select').forEach(input => { const name = input.getAttribute('name'); if (name) { const baseName = name.replace(/\d+$/, ''); input.setAttribute('name', baseName + newIndex); } }); }); } function createIdentificationHTML(index, type, value = '', schemeId = '') { const typeInfo = IDENTIFICATION_TYPES[type]; const id = `${type.toLowerCase()}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; if (type === 'COMMODITY') { return `
`; } return `
${type === 'STANDARD' ? `` : ''}
`; } window.addIdentification = function(lineItemIndex, type) { const container = document.querySelector(`#identifications${lineItemIndex}`); if (container) { container.insertAdjacentHTML('beforeend', createIdentificationHTML(lineItemIndex, type)); validateIdentifications(lineItemIndex); } } window.removeIdentification = function(id) { const element = document.querySelector(`[data-id="${id}"]`); if (element) { const lineItem = element.closest('.line-item'); element.remove(); if (lineItem) { validateIdentifications(parseInt(lineItem.dataset.index)); } } }; // Update XML parsing function parseIdentifications(itemElement, lineItemIndex) { // console.log("Parsing identifications for line", lineItemIndex); const container = document.querySelector(`#identifications${lineItemIndex}`); if (!container) return; const listContainer = container.querySelector('.identifications-list'); listContainer.innerHTML = ''; // Parse BuyersItemIdentification const buyersId = itemElement.querySelector('cac\\:BuyersItemIdentification cbc\\:ID, BuyersItemIdentification ID'); if (buyersId) { listContainer.insertAdjacentHTML('beforeend', createIdentificationHTML(lineItemIndex, 'BUYERS', buyersId.textContent) ); } // Parse SellersItemIdentification const sellersId = itemElement.querySelector('cac\\:SellersItemIdentification cbc\\:ID, SellersItemIdentification ID'); if (sellersId) { listContainer.insertAdjacentHTML('beforeend', createIdentificationHTML(lineItemIndex, 'SELLERS', sellersId.textContent) ); } // Parse StandardItemIdentification const standardId = itemElement.querySelector('cac\\:StandardItemIdentification cbc\\:ID, StandardItemIdentification ID'); if (standardId) { listContainer.insertAdjacentHTML('beforeend', createIdentificationHTML(lineItemIndex, 'STANDARD', standardId.textContent) ); } // Parse CommodityClassifications const commodityClassifications = itemElement.querySelectorAll('cac\\:CommodityClassification cbc\\:ItemClassificationCode, CommodityClassification ItemClassificationCode'); commodityClassifications.forEach(classification => { const listId = classification.getAttribute('listID') || 'CV'; const code = classification.textContent; if (code && listId) { listContainer.insertAdjacentHTML('beforeend', createIdentificationHTML(lineItemIndex, 'COMMODITY', code, listId) ); } }); } // Update XML saving function saveIdentificationsToXML(xmlDoc, itemElement, lineItemIndex) { const container = document.querySelector(`#identifications${lineItemIndex}`); if (!container) return; container.querySelectorAll('.identification-row').forEach(row => { const type = row.dataset.type; const id = row.dataset.id; const schemeInput = document.querySelector(`[name="scheme_${id}"]`); const valueInput = document.querySelector(`[name="value_${id}"]`); if (!valueInput?.value) return; if (type === 'COMMODITY') { const schemeSelect = document.querySelector(`[name="scheme_${id}"]`); if (!schemeSelect) return; const identificationElement = createXMLElement(xmlDoc, XML_NAMESPACES.cac, 'cac:CommodityClassification'); const idElement = createXMLElement(xmlDoc, XML_NAMESPACES.cbc, 'cbc:ItemClassificationCode', valueInput.value); idElement.setAttribute('listID', schemeSelect.value); identificationElement.appendChild(idElement); itemElement.appendChild(identificationElement); } else { const typeInfo = IDENTIFICATION_TYPES[type]; const identificationElement = createXMLElement(xmlDoc, XML_NAMESPACES.cac, `cac:${typeInfo.xmlTag}`); const idElement = createXMLElement(xmlDoc, XML_NAMESPACES.cbc, 'cbc:ID', valueInput.value); if (type === 'STANDARD') { idElement.setAttribute('schemeID', '0160'); } identificationElement.appendChild(idElement); itemElement.appendChild(identificationElement); } }); } // Add validation function validateIdentifications(lineItemIndex) { const container = document.querySelector(`#identifications${lineItemIndex}`); if (!container) return true; let isValid = true; container.querySelectorAll('.identification-row').forEach(row => { const type = row.dataset.type; const id = row.dataset.id; const input = row.querySelector(`[name="value_${id}"]`); if (type === 'COMMODITY') { const scheme = row.querySelector('.scheme-select').value; if (scheme === 'TSP' && !/^\d{8}-\d$/.test(input.value)) { input.classList.add('invalid'); isValid = false; } else if (scheme === 'STI' && !/^\d{8}$/.test(input.value)) { input.classList.add('invalid'); isValid = false; } else { input.classList.remove('invalid'); } } }); return isValid; } // Export for testing if needed if (typeof module !== 'undefined' && module.exports) { module.exports = { calculateTotals, validateForm, roundNumber, formatXML, createXMLElement, getXMLValue, setXMLValue, formatter, setupInlineEditing, updateTotalDisplay, displayTotals, updateVATDisplay, getDisplayValue }; } import { InvoicePrintHandler } from './print.js'; // Create print handler instance const printHandler = new InvoicePrintHandler(); // Initialize when the document is ready document.addEventListener('DOMContentLoaded', () => { // Add print controls to the UI 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 = ` `; templateSelect.addEventListener('change', (e) => { printHandler.setTemplate(e.target.value); }); // Create print button const printButton = document.createElement('button'); printButton.className = 'button'; printButton.onclick = () => printHandler.print(); printButton.innerHTML = 'Printează'; // Add elements to controls printControls.appendChild(templateSelect); printControls.appendChild(printButton); // Add controls to header headerButtonGroup.appendChild(printControls); } });