1import { 2 COMPLIANCE_SEVERITY, 3 COMPLIANCE_STATUS, 4 createComplianceDiagnostic 5} from '../contracts.84ed5d8e89eb.js'; 6import { normalizeComplianceSupplement } from '../semantic/supplement.0004a4d51d32.js'; 7 8const NORWAY_PEPPOL_EVIDENCE = Object.freeze([ 9 Object.freeze({ 10 authority: 'OpenPeppol', 11 title: 'Peppol BIS Billing 3.0 â Norway rule NO-R-001', 12 url: 'https://docs.peppol.eu/poacc/billing/3.0/upcoming/rules/ubl-peppol/NO-R-001/', 13 reviewedAt: '2026-08-16' 14 }), 15 Object.freeze({ 16 authority: 'OpenPeppol', 17 title: 'Peppol BIS Billing 3.0 â Norway rule NO-R-002', 18 url: 'https://docs.peppol.eu/poacc/billing/3.0/upcoming/rules/ubl-peppol/NO-R-002/', 19 reviewedAt: '2026-08-16' 20 }), 21 Object.freeze({ 22 authority: 'Altinn', 23 title: 'Value added tax â no separate Norwegian VAT number', 24 url: 'https://info.altinn.no/en/start-and-run-business/direct-and-indirect-taxes/indirect-taxes/value-added-tax', 25 reviewedAt: '2026-08-16' 26 }) 27]); 28 29function digits(value) { 30 return String(value || '').replace(/\D/g, ''); 31} 32 33export function isValidNorwegianOrganizationNumber(value) { 34 const normalized = digits(value); 35 if (!/^\d{9}$/.test(normalized)) return false; 36 const weights = [3, 2, 7, 6, 5, 4, 3, 2]; 37 const sum = weights.reduce((total, weight, index) => total + (Number(normalized[index]) * weight), 0); 38 const remainder = sum % 11; 39 const check = remainder === 0 ? 0 : 11 - remainder; 40 return check !== 10 && Number(normalized[8]) === check; 41} 42 43export function buildNorwayVatIdentifier(organizationNumber) { 44 const normalized = digits(organizationNumber); 45 if (!isValidNorwegianOrganizationNumber(normalized)) return ''; 46 return `NO${normalized}MVA`; 47} 48 49function copyParty(party) { 50 return { 51 postalAddress: { ...(party?.postalAddress || {}) }, 52 electronicAddressSchemeId: party?.electronicAddressSchemeId || '', 53 taxId: { ...(party?.taxId || {}) }, 54 vatIdentifier: party?.vatIdentifier || '', 55 taxRegistrationIdentifier: { ...(party?.taxRegistrationIdentifier || {}) } 56 }; 57} 58 59export function buildNorwayPeppolSupplement(snapshot, supplementInput = {}) { 60 const base = normalizeComplianceSupplement(supplementInput); 61 const seller = copyParty(base.seller); 62 const buyer = copyParty(base.buyer); 63 const organizationNumber = digits(snapshot?.seller?.taxId); 64 const validOrganizationNumber = isValidNorwegianOrganizationNumber(organizationNumber); 65 66 if (validOrganizationNumber && !seller.taxId.role) { 67 seller.taxId = { 68 role: 'legal-registration', 69 schemeId: '0192', 70 taxSchemeId: '' 71 }; 72 } 73 74 if ( 75 validOrganizationNumber 76 && snapshot?.seller?.registration?.vatStatus === 'registered' 77 && !seller.vatIdentifier 78 ) { 79 seller.vatIdentifier = buildNorwayVatIdentifier(organizationNumber); 80 } 81 82 const legalForm = String(snapshot?.seller?.registration?.legalForm || '').toLowerCase(); 83 const businessRegisterStatus = String(snapshot?.seller?.registration?.businessRegisterStatus || '').toLowerCase(); 84 if ( 85 ['as', 'asa', 'nuf'].includes(legalForm) 86 && businessRegisterStatus === 'registered' 87 && !seller.taxRegistrationIdentifier.value 88 ) { 89 seller.taxRegistrationIdentifier = { 90 value: 'Foretaksregisteret', 91 taxSchemeId: 'TAX' 92 }; 93 } 94 95 return normalizeComplianceSupplement({ 96 version: 1, 97 buyerReference: base.buyerReference, 98 seller, 99 buyer, 100 lines: base.lines.map((line) => ({ ...line })) 101 }); 102} 103 104function organizationNumberDiagnostic(value, path, id) { 105 if (!value) return null; 106 return createComplianceDiagnostic({ 107 id, 108 status: isValidNorwegianOrganizationNumber(value) ? COMPLIANCE_STATUS.PASS : COMPLIANCE_STATUS.FAIL, 109 severity: isValidNorwegianOrganizationNumber(value) ? COMPLIANCE_SEVERITY.INFO : COMPLIANCE_SEVERITY.ERROR, 110 message: isValidNorwegianOrganizationNumber(value) 111 ? 'Norwegian organisation number passes the modulus-11 structural check.' 112 : 'Norwegian organisation number must be nine digits with a valid modulus-11 check digit.', 113 path, 114 capability: 'norway-peppol-identifier-format' 115 }); 116} 117 118export function evaluateNorwayPeppolOverlay(snapshot, semanticResult) { 119 const diagnostics = []; 120 const semanticInvoice = semanticResult?.invoice || null; 121 if (!semanticInvoice) { 122 diagnostics.push(createComplianceDiagnostic({ 123 id: 'NO-PEPPOL-SEMANTIC-RESULT-MISSING', 124 status: COMPLIANCE_STATUS.UNSUPPORTED, 125 severity: COMPLIANCE_SEVERITY.WARNING, 126 message: 'Norway Peppol overlay requires an EN16931 semantic result.', 127 capability: 'norway-peppol-overlay' 128 })); 129 return Object.freeze({ diagnostics: Object.freeze(diagnostics), evidence: NORWAY_PEPPOL_EVIDENCE }); 130 } 131 132 const sellerCountry = semanticInvoice.seller?.address?.countryCode || ''; 133 if (sellerCountry && sellerCountry !== 'NO') { 134 diagnostics.push(createComplianceDiagnostic({ 135 id: 'NO-PEPPOL-SELLER-COUNTRY', 136 status: COMPLIANCE_STATUS.UNSUPPORTED, 137 severity: COMPLIANCE_SEVERITY.WARNING, 138 message: `Norway country overlay does not apply to seller country ${sellerCountry}.`, 139 path: 'seller.address.countryCode', 140 capability: 'norway-peppol-overlay' 141 })); 142 return Object.freeze({ diagnostics: Object.freeze(diagnostics), evidence: NORWAY_PEPPOL_EVIDENCE }); 143 } 144 145 const vatIdentifier = semanticInvoice.seller?.identifiers?.vatIdentifier?.value || ''; 146 if (vatIdentifier) { 147 const match = /^NO(\d{9})MVA$/.exec(vatIdentifier); 148 const valid = Boolean(match && isValidNorwegianOrganizationNumber(match[1])); 149 diagnostics.push(createComplianceDiagnostic({ 150 id: 'NO-R-001', 151 status: valid ? COMPLIANCE_STATUS.PASS : COMPLIANCE_STATUS.FAIL, 152 severity: valid ? COMPLIANCE_SEVERITY.INFO : COMPLIANCE_SEVERITY.ERROR, 153 message: valid 154 ? 'Norwegian seller VAT identifier matches NO + valid nine-digit organisation number + MVA.'
155 : 'Norwegian seller VAT identifier must be NO followed by a valid nine-digit organisation number and MVA.', 156 path: 'seller.vatIdentifier', 157 capability: 'norway-peppol-country-rule' 158 })); 159 } 160 161 const legalId = semanticInvoice.seller?.identifiers?.legalRegistrationIdentifier; 162 if (legalId?.schemeId === '0192') { 163 const diagnostic = organizationNumberDiagnostic(legalId.value, 'seller.taxId', 'PEPPOL-COMMON-R041-SELLER-LEGAL-ID'); 164 if (diagnostic) diagnostics.push(diagnostic); 165 } 166 for (const [role, endpoint] of [ 167 ['seller', semanticInvoice.seller?.electronicAddress], 168 ['buyer', semanticInvoice.buyer?.electronicAddress] 169 ]) { 170 if (endpoint?.schemeId === '0192') { 171 const diagnostic = organizationNumberDiagnostic(endpoint.value, `${role}.electronicAddress`, `PEPPOL-COMMON-R041-${role.toUpperCase()}-ENDPOINT`); 172 if (diagnostic) diagnostics.push(diagnostic); 173 } 174 } 175 176 const legalForm = String(snapshot?.seller?.registration?.legalForm || '').toLowerCase(); 177 if (['as', 'asa', 'nuf'].includes(legalForm)) { 178 const taxRegistration = semanticInvoice.seller?.identifiers?.taxRegistrationIdentifier; 179 const hasRegisterText = taxRegistration?.value === 'Foretaksregisteret' && taxRegistration?.taxSchemeId === 'TAX'; 180 diagnostics.push(createComplianceDiagnostic({ 181 id: 'NO-R-002', 182 status: hasRegisterText ? COMPLIANCE_STATUS.PASS : COMPLIANCE_STATUS.REVIEW_REQUIRED, 183 severity: hasRegisterText ? COMPLIANCE_SEVERITY.INFO : COMPLIANCE_SEVERITY.WARNING, 184 message: hasRegisterText 185 ? 'Norwegian company-register presentation is represented as Foretaksregisteret with TAX scheme.' 186 : 'Review the Norwegian Foretaksregisteret presentation requirement for this legal form.', 187 path: 'seller.registration.businessRegisterStatus', 188 capability: 'norway-peppol-country-rule' 189 })); 190 } 191 192 return Object.freeze({ 193 diagnostics: Object.freeze(diagnostics), 194 evidence: NORWAY_PEPPOL_EVIDENCE 195 }); 196} 197 198export { NORWAY_PEPPOL_EVIDENCE };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.