Files
einvoice/ts/formats/validation/vat-categories.validator.ts
Juergen Kunz 10e14af85b feat(validation): Implement EN16931 compliance validation types and VAT categories
- Added validation types for EN16931 compliance in `validation.types.ts`, including interfaces for `ValidationResult`, `ValidationOptions`, and `ValidationReport`.
- Introduced `VATCategoriesValidator` in `vat-categories.validator.ts` to validate VAT categories according to EN16931 rules, including detailed checks for standard, zero-rated, exempt, reverse charge, intra-community, export, and out-of-scope services.
- Enhanced `IEInvoiceMetadata` interface in `en16931-metadata.ts` to include additional fields required for full standards compliance, such as delivery information, payment information, allowances, and charges.
- Implemented helper methods for VAT calculations and validation logic to ensure accurate compliance with EN16931 standards.
2025-08-11 12:25:32 +00:00

845 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as plugins from '../../plugins.js';
import type { TAccountingDocItem } from '@tsclass/tsclass/dist_ts/finance/index.js';
import type { EInvoice } from '../../einvoice.js';
import { CurrencyCalculator } from '../utils/currency.utils.js';
import type { ValidationResult } from './validation.types.js';
/**
* VAT Category codes according to UNCL5305
*/
export enum VATCategory {
S = 'S', // Standard rate
Z = 'Z', // Zero rated
E = 'E', // Exempt from tax
AE = 'AE', // VAT Reverse Charge
K = 'K', // VAT exempt for EEA intra-community supply
G = 'G', // Free export outside EU
O = 'O', // Services outside scope of tax
L = 'L', // Canary Islands general indirect tax
M = 'M' // Tax for production, services and importation in Ceuta and Melilla
}
/**
* Extended VAT information for EN16931
*/
export interface VATBreakdown {
category: VATCategory;
rate: number;
taxableAmount: number;
taxAmount: number;
exemptionReason?: string;
exemptionReasonCode?: string;
}
/**
* Comprehensive VAT Category Rules Validator
* Implements all EN16931 VAT category-specific business rules
*/
export class VATCategoriesValidator {
private results: ValidationResult[] = [];
private currencyCalculator?: CurrencyCalculator;
/**
* Validate VAT categories according to EN16931
*/
public validate(invoice: EInvoice): ValidationResult[] {
this.results = [];
// Initialize currency calculator if currency is available
if (invoice.currency) {
this.currencyCalculator = new CurrencyCalculator(invoice.currency);
}
// Group items by VAT category
const itemsByCategory = this.groupItemsByVATCategory(invoice.items || []);
const breakdownsByCategory = this.groupBreakdownsByCategory(invoice.taxBreakdown || []);
// Validate each VAT category
this.validateStandardRate(itemsByCategory.get('S'), breakdownsByCategory.get('S'), invoice);
this.validateZeroRated(itemsByCategory.get('Z'), breakdownsByCategory.get('Z'), invoice);
this.validateExempt(itemsByCategory.get('E'), breakdownsByCategory.get('E'), invoice);
this.validateReverseCharge(itemsByCategory.get('AE'), breakdownsByCategory.get('AE'), invoice);
this.validateIntraCommunity(itemsByCategory.get('K'), breakdownsByCategory.get('K'), invoice);
this.validateExport(itemsByCategory.get('G'), breakdownsByCategory.get('G'), invoice);
this.validateOutOfScope(itemsByCategory.get('O'), breakdownsByCategory.get('O'), invoice);
// Cross-category validation
this.validateCrossCategoryRules(invoice, itemsByCategory, breakdownsByCategory);
return this.results;
}
/**
* Validate Standard Rate VAT (BR-S-*)
*/
private validateStandardRate(
items?: TAccountingDocItem[],
breakdown?: any,
invoice?: EInvoice
): void {
if (!items || items.length === 0) return;
// BR-S-01: Invoice with standard rated items must have standard rated breakdown
if (!breakdown) {
this.addError('BR-S-01',
'Invoice with standard rated items must have a standard rated VAT breakdown',
'taxBreakdown'
);
return;
}
// BR-S-02: Standard rate VAT category taxable amount
const expectedTaxable = this.calculateTaxableAmount(items);
if (!this.areAmountsEqual(breakdown.netAmount, expectedTaxable)) {
this.addError('BR-S-02',
`Standard rate VAT taxable amount mismatch`,
'taxBreakdown.netAmount',
breakdown.netAmount,
expectedTaxable
);
}
// BR-S-03: Standard rate VAT category tax amount
const rate = breakdown.taxPercent || 0;
const expectedTax = this.calculateVATAmount(expectedTaxable, rate);
if (!this.areAmountsEqual(breakdown.taxAmount, expectedTax)) {
this.addError('BR-S-03',
`Standard rate VAT tax amount mismatch`,
'taxBreakdown.taxAmount',
breakdown.taxAmount,
expectedTax
);
}
// BR-S-04: Standard rate VAT category code must be "S"
if (breakdown.categoryCode && breakdown.categoryCode !== 'S') {
this.addError('BR-S-04',
'Standard rate VAT category code must be "S"',
'taxBreakdown.categoryCode',
breakdown.categoryCode,
'S'
);
}
// BR-S-05: Standard rate VAT rate must be greater than zero
if (rate <= 0) {
this.addError('BR-S-05',
'Standard rate VAT rate must be greater than zero',
'taxBreakdown.taxPercent',
rate,
'> 0'
);
}
// BR-S-08: No exemption reason for standard rate
if (breakdown.exemptionReason) {
this.addError('BR-S-08',
'Standard rate VAT must not have an exemption reason',
'taxBreakdown.exemptionReason'
);
}
}
/**
* Validate Zero Rated VAT (BR-Z-*)
*/
private validateZeroRated(
items?: TAccountingDocItem[],
breakdown?: any,
invoice?: EInvoice
): void {
if (!items || items.length === 0) return;
// BR-Z-01: Invoice with zero rated items must have zero rated breakdown
if (!breakdown) {
this.addError('BR-Z-01',
'Invoice with zero rated items must have a zero rated VAT breakdown',
'taxBreakdown'
);
return;
}
// BR-Z-02: Zero rate VAT category taxable amount
const expectedTaxable = this.calculateTaxableAmount(items);
if (!this.areAmountsEqual(breakdown.netAmount, expectedTaxable)) {
this.addError('BR-Z-02',
'Zero rate VAT taxable amount mismatch',
'taxBreakdown.netAmount',
breakdown.netAmount,
expectedTaxable
);
}
// BR-Z-03: Zero rate VAT tax amount must be zero
if (breakdown.taxAmount !== 0) {
this.addError('BR-Z-03',
'Zero rate VAT tax amount must be zero',
'taxBreakdown.taxAmount',
breakdown.taxAmount,
0
);
}
// BR-Z-04: Zero rate VAT category code must be "Z"
if (breakdown.categoryCode && breakdown.categoryCode !== 'Z') {
this.addError('BR-Z-04',
'Zero rate VAT category code must be "Z"',
'taxBreakdown.categoryCode',
breakdown.categoryCode,
'Z'
);
}
// BR-Z-05: Zero rate VAT rate must be zero
if (breakdown.taxPercent !== 0) {
this.addError('BR-Z-05',
'Zero rate VAT rate must be zero',
'taxBreakdown.taxPercent',
breakdown.taxPercent,
0
);
}
}
/**
* Validate Exempt from Tax (BR-E-*)
*/
private validateExempt(
items?: TAccountingDocItem[],
breakdown?: any,
invoice?: EInvoice
): void {
if (!items || items.length === 0) return;
// BR-E-01: Invoice with exempt items must have exempt breakdown
if (!breakdown) {
this.addError('BR-E-01',
'Invoice with tax exempt items must have an exempt VAT breakdown',
'taxBreakdown'
);
return;
}
// BR-E-02: Exempt VAT category taxable amount
const expectedTaxable = this.calculateTaxableAmount(items);
if (!this.areAmountsEqual(breakdown.netAmount, expectedTaxable)) {
this.addError('BR-E-02',
'Exempt VAT taxable amount mismatch',
'taxBreakdown.netAmount',
breakdown.netAmount,
expectedTaxable
);
}
// BR-E-03: Exempt VAT tax amount must be zero
if (breakdown.taxAmount !== 0) {
this.addError('BR-E-03',
'Exempt VAT tax amount must be zero',
'taxBreakdown.taxAmount',
breakdown.taxAmount,
0
);
}
// BR-E-04: Exempt VAT category code must be "E"
if (breakdown.categoryCode && breakdown.categoryCode !== 'E') {
this.addError('BR-E-04',
'Exempt VAT category code must be "E"',
'taxBreakdown.categoryCode',
breakdown.categoryCode,
'E'
);
}
// BR-E-05: Exempt VAT rate must be zero
if (breakdown.taxPercent !== 0) {
this.addError('BR-E-05',
'Exempt VAT rate must be zero',
'taxBreakdown.taxPercent',
breakdown.taxPercent,
0
);
}
// BR-E-06: Exempt VAT must have exemption reason
if (!breakdown.exemptionReason && !breakdown.exemptionReasonCode) {
this.addError('BR-E-06',
'Exempt VAT must have an exemption reason or exemption reason code',
'taxBreakdown.exemptionReason'
);
}
}
/**
* Validate VAT Reverse Charge (BR-AE-*)
*/
private validateReverseCharge(
items?: TAccountingDocItem[],
breakdown?: any,
invoice?: EInvoice
): void {
if (!items || items.length === 0) return;
// BR-AE-01: Invoice with reverse charge items must have reverse charge breakdown
if (!breakdown) {
this.addError('BR-AE-01',
'Invoice with reverse charge items must have a reverse charge VAT breakdown',
'taxBreakdown'
);
return;
}
// BR-AE-02: Reverse charge VAT category taxable amount
const expectedTaxable = this.calculateTaxableAmount(items);
if (!this.areAmountsEqual(breakdown.netAmount, expectedTaxable)) {
this.addError('BR-AE-02',
'Reverse charge VAT taxable amount mismatch',
'taxBreakdown.netAmount',
breakdown.netAmount,
expectedTaxable
);
}
// BR-AE-03: Reverse charge VAT tax amount must be zero
if (breakdown.taxAmount !== 0) {
this.addError('BR-AE-03',
'Reverse charge VAT tax amount must be zero',
'taxBreakdown.taxAmount',
breakdown.taxAmount,
0
);
}
// BR-AE-04: Reverse charge VAT category code must be "AE"
if (breakdown.categoryCode && breakdown.categoryCode !== 'AE') {
this.addError('BR-AE-04',
'Reverse charge VAT category code must be "AE"',
'taxBreakdown.categoryCode',
breakdown.categoryCode,
'AE'
);
}
// BR-AE-05: Reverse charge VAT rate must be zero
if (breakdown.taxPercent !== 0) {
this.addError('BR-AE-05',
'Reverse charge VAT rate must be zero',
'taxBreakdown.taxPercent',
breakdown.taxPercent,
0
);
}
// BR-AE-06: Reverse charge must have exemption reason
if (!breakdown.exemptionReason && !breakdown.exemptionReasonCode) {
this.addError('BR-AE-06',
'Reverse charge VAT must have an exemption reason',
'taxBreakdown.exemptionReason'
);
}
// BR-AE-08: Buyer must have VAT identifier for reverse charge
if (!invoice?.metadata?.buyerTaxId) {
this.addError('BR-AE-08',
'Buyer must have a VAT identifier for reverse charge invoices',
'metadata.buyerTaxId'
);
}
}
/**
* Validate Intra-Community Supply (BR-K-*)
*/
private validateIntraCommunity(
items?: TAccountingDocItem[],
breakdown?: any,
invoice?: EInvoice
): void {
if (!items || items.length === 0) return;
// BR-K-01: Invoice with intra-community items must have intra-community breakdown
if (!breakdown) {
this.addError('BR-K-01',
'Invoice with intra-community supply must have corresponding VAT breakdown',
'taxBreakdown'
);
return;
}
// BR-K-02: Intra-community VAT category taxable amount
const expectedTaxable = this.calculateTaxableAmount(items);
if (!this.areAmountsEqual(breakdown.netAmount, expectedTaxable)) {
this.addError('BR-K-02',
'Intra-community VAT taxable amount mismatch',
'taxBreakdown.netAmount',
breakdown.netAmount,
expectedTaxable
);
}
// BR-K-03: Intra-community VAT tax amount must be zero
if (breakdown.taxAmount !== 0) {
this.addError('BR-K-03',
'Intra-community VAT tax amount must be zero',
'taxBreakdown.taxAmount',
breakdown.taxAmount,
0
);
}
// BR-K-04: Intra-community VAT category code must be "K"
if (breakdown.categoryCode && breakdown.categoryCode !== 'K') {
this.addError('BR-K-04',
'Intra-community VAT category code must be "K"',
'taxBreakdown.categoryCode',
breakdown.categoryCode,
'K'
);
}
// BR-K-05: Intra-community VAT rate must be zero
if (breakdown.taxPercent !== 0) {
this.addError('BR-K-05',
'Intra-community VAT rate must be zero',
'taxBreakdown.taxPercent',
breakdown.taxPercent,
0
);
}
// BR-K-06: Must have exemption reason
if (!breakdown.exemptionReason && !breakdown.exemptionReasonCode) {
this.addError('BR-K-06',
'Intra-community supply must have an exemption reason',
'taxBreakdown.exemptionReason'
);
}
// BR-K-08: Both seller and buyer must have VAT identifiers
if (!invoice?.metadata?.sellerTaxId) {
this.addError('BR-K-08',
'Seller must have a VAT identifier for intra-community supply',
'metadata.sellerTaxId'
);
}
if (!invoice?.metadata?.buyerTaxId) {
this.addError('BR-K-09',
'Buyer must have a VAT identifier for intra-community supply',
'metadata.buyerTaxId'
);
}
// BR-K-10: Must be in different EU member states
if (invoice?.from?.address?.countryCode === invoice?.to?.address?.countryCode) {
this.addWarning('BR-K-10',
'Intra-community supply should be between different EU member states',
'address.countryCode'
);
}
}
/**
* Validate Export Outside EU (BR-G-*)
*/
private validateExport(
items?: TAccountingDocItem[],
breakdown?: any,
invoice?: EInvoice
): void {
if (!items || items.length === 0) return;
// BR-G-01: Invoice with export items must have export breakdown
if (!breakdown) {
this.addError('BR-G-01',
'Invoice with export items must have an export VAT breakdown',
'taxBreakdown'
);
return;
}
// BR-G-02: Export VAT category taxable amount
const expectedTaxable = this.calculateTaxableAmount(items);
if (!this.areAmountsEqual(breakdown.netAmount, expectedTaxable)) {
this.addError('BR-G-02',
'Export VAT taxable amount mismatch',
'taxBreakdown.netAmount',
breakdown.netAmount,
expectedTaxable
);
}
// BR-G-03: Export VAT tax amount must be zero
if (breakdown.taxAmount !== 0) {
this.addError('BR-G-03',
'Export VAT tax amount must be zero',
'taxBreakdown.taxAmount',
breakdown.taxAmount,
0
);
}
// BR-G-04: Export VAT category code must be "G"
if (breakdown.categoryCode && breakdown.categoryCode !== 'G') {
this.addError('BR-G-04',
'Export VAT category code must be "G"',
'taxBreakdown.categoryCode',
breakdown.categoryCode,
'G'
);
}
// BR-G-05: Export VAT rate must be zero
if (breakdown.taxPercent !== 0) {
this.addError('BR-G-05',
'Export VAT rate must be zero',
'taxBreakdown.taxPercent',
breakdown.taxPercent,
0
);
}
// BR-G-06: Must have exemption reason
if (!breakdown.exemptionReason && !breakdown.exemptionReasonCode) {
this.addError('BR-G-06',
'Export must have an exemption reason',
'taxBreakdown.exemptionReason'
);
}
// BR-G-08: Buyer should be outside EU
const buyerCountry = invoice?.to?.address?.countryCode;
if (buyerCountry && this.isEUCountry(buyerCountry)) {
this.addWarning('BR-G-08',
'Export category should be used for buyers outside EU',
'to.address.countryCode',
buyerCountry,
'non-EU'
);
}
}
/**
* Validate Out of Scope Services (BR-O-*)
*/
private validateOutOfScope(
items?: TAccountingDocItem[],
breakdown?: any,
invoice?: EInvoice
): void {
if (!items || items.length === 0) return;
// BR-O-01: Invoice with out of scope items must have out of scope breakdown
if (!breakdown) {
this.addError('BR-O-01',
'Invoice with out of scope items must have corresponding VAT breakdown',
'taxBreakdown'
);
return;
}
// BR-O-02: Out of scope VAT category taxable amount
const expectedTaxable = this.calculateTaxableAmount(items);
if (!this.areAmountsEqual(breakdown.netAmount, expectedTaxable)) {
this.addError('BR-O-02',
'Out of scope VAT taxable amount mismatch',
'taxBreakdown.netAmount',
breakdown.netAmount,
expectedTaxable
);
}
// BR-O-03: Out of scope VAT tax amount must be zero
if (breakdown.taxAmount !== 0) {
this.addError('BR-O-03',
'Out of scope VAT tax amount must be zero',
'taxBreakdown.taxAmount',
breakdown.taxAmount,
0
);
}
// BR-O-04: Out of scope VAT category code must be "O"
if (breakdown.categoryCode && breakdown.categoryCode !== 'O') {
this.addError('BR-O-04',
'Out of scope VAT category code must be "O"',
'taxBreakdown.categoryCode',
breakdown.categoryCode,
'O'
);
}
// BR-O-05: Out of scope VAT rate must be zero
if (breakdown.taxPercent !== 0) {
this.addError('BR-O-05',
'Out of scope VAT rate must be zero',
'taxBreakdown.taxPercent',
breakdown.taxPercent,
0
);
}
// BR-O-06: Must have exemption reason
if (!breakdown.exemptionReason && !breakdown.exemptionReasonCode) {
this.addError('BR-O-06',
'Out of scope services must have an exemption reason',
'taxBreakdown.exemptionReason'
);
}
}
/**
* Cross-category validation rules
*/
private validateCrossCategoryRules(
invoice: EInvoice,
itemsByCategory: Map<string, TAccountingDocItem[]>,
breakdownsByCategory: Map<string, any>
): void {
// BR-CO-17: VAT category tax amount = Σ(VAT category taxable amount × VAT rate)
breakdownsByCategory.forEach((breakdown, category) => {
if (category === 'S' && breakdown.taxPercent > 0) {
const expectedTax = this.calculateVATAmount(breakdown.netAmount, breakdown.taxPercent);
if (!this.areAmountsEqual(breakdown.taxAmount, expectedTax)) {
this.addError('BR-CO-17',
`VAT tax amount calculation error for category ${category}`,
'taxBreakdown.taxAmount',
breakdown.taxAmount,
expectedTax
);
}
}
});
// BR-CO-18: Invoice with mixed VAT categories
const categoriesUsed = new Set<string>();
itemsByCategory.forEach((items, category) => {
if (items.length > 0) categoriesUsed.add(category);
});
// BR-IC-01: Supply to EU countries without VAT ID should use standard rate
if (categoriesUsed.has('K') && !invoice.metadata?.buyerTaxId) {
this.addError('BR-IC-01',
'Intra-community supply requires buyer VAT identifier',
'metadata.buyerTaxId'
);
}
// BR-IC-02: Reverse charge requires specific conditions
if (categoriesUsed.has('AE')) {
// Check for service codes that qualify for reverse charge
const hasQualifyingServices = invoice.items?.some(item =>
this.isReverseChargeService(item)
);
if (!hasQualifyingServices) {
this.addWarning('BR-IC-02',
'Reverse charge should only be used for qualifying services',
'items'
);
}
}
// BR-CO-19: Sum of VAT breakdown taxable amounts must equal invoice tax exclusive total
let totalTaxable = 0;
breakdownsByCategory.forEach(breakdown => {
totalTaxable += breakdown.netAmount || 0;
});
const declaredTotal = invoice.totalNet || 0;
if (!this.areAmountsEqual(totalTaxable, declaredTotal)) {
this.addError('BR-CO-19',
'Sum of VAT breakdown taxable amounts must equal invoice total without VAT',
'totalNet',
declaredTotal,
totalTaxable
);
}
}
// Helper methods
private groupItemsByVATCategory(items: TAccountingDocItem[]): Map<string, TAccountingDocItem[]> {
const groups = new Map<string, TAccountingDocItem[]>();
items.forEach(item => {
const category = this.determineVATCategory(item);
if (!groups.has(category)) {
groups.set(category, []);
}
groups.get(category)!.push(item);
});
return groups;
}
private groupBreakdownsByCategory(breakdowns: any[]): Map<string, any> {
const groups = new Map<string, any>();
breakdowns.forEach(breakdown => {
const category = breakdown.categoryCode || this.inferCategoryFromRate(breakdown.taxPercent);
groups.set(category, breakdown);
});
return groups;
}
private determineVATCategory(item: TAccountingDocItem): string {
// Determine VAT category from item metadata or rate
const metadata = (item as any).metadata;
if (metadata?.vatCategory) {
return metadata.vatCategory;
}
// Infer from rate
if (item.vatPercentage === undefined || item.vatPercentage === null) {
return 'S'; // Default to standard
} else if (item.vatPercentage > 0) {
return 'S'; // Standard rate
} else if (item.vatPercentage === 0) {
// Could be Z, E, AE, K, G, or O - need more context
if (metadata?.exemptionReason) {
if (metadata.exemptionReason.includes('reverse')) return 'AE';
if (metadata.exemptionReason.includes('intra')) return 'K';
if (metadata.exemptionReason.includes('export')) return 'G';
if (metadata.exemptionReason.includes('scope')) return 'O';
return 'E'; // Default exempt
}
return 'Z'; // Default zero-rated
}
return 'S'; // Default
}
private inferCategoryFromRate(rate?: number): string {
if (!rate || rate === 0) return 'Z';
if (rate > 0) return 'S';
return 'S';
}
private calculateTaxableAmount(items: TAccountingDocItem[]): number {
const total = items.reduce((sum, item) => {
const lineNet = (item.unitNetPrice || 0) * (item.unitQuantity || 0);
return sum + (this.currencyCalculator ? this.currencyCalculator.round(lineNet) : lineNet);
}, 0);
return this.currencyCalculator ? this.currencyCalculator.round(total) : total;
}
private calculateVATAmount(taxableAmount: number, rate: number): number {
const vat = taxableAmount * (rate / 100);
return this.currencyCalculator ? this.currencyCalculator.round(vat) : vat;
}
private areAmountsEqual(value1: number, value2: number): boolean {
if (this.currencyCalculator) {
return this.currencyCalculator.areEqual(value1, value2);
}
return Math.abs(value1 - value2) < 0.01;
}
private isEUCountry(countryCode: string): boolean {
const euCountries = [
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE'
];
return euCountries.includes(countryCode);
}
private isReverseChargeService(item: TAccountingDocItem): boolean {
// Check if item qualifies for reverse charge
// This would typically check service codes
const metadata = (item as any).metadata;
if (metadata?.serviceCode) {
// Construction services, telecommunication, etc.
const reverseChargeServices = ['44', '45', '61', '62'];
return reverseChargeServices.some(code =>
metadata.serviceCode.startsWith(code)
);
}
return false;
}
private addError(
ruleId: string,
message: string,
field?: string,
value?: any,
expected?: any
): void {
this.results.push({
ruleId,
source: 'EN16931',
severity: 'error',
message,
field,
value,
expected,
btReference: this.getBTReference(ruleId),
bgReference: 'BG-23' // VAT breakdown
});
}
private addWarning(
ruleId: string,
message: string,
field?: string,
value?: any,
expected?: any
): void {
this.results.push({
ruleId,
source: 'EN16931',
severity: 'warning',
message,
field,
value,
expected,
btReference: this.getBTReference(ruleId),
bgReference: 'BG-23'
});
}
private getBTReference(ruleId: string): string | undefined {
const btMap: Record<string, string> = {
'BR-S-': 'BT-118', // VAT category rate
'BR-Z-': 'BT-118',
'BR-E-': 'BT-120', // VAT exemption reason
'BR-AE-': 'BT-120',
'BR-K-': 'BT-120',
'BR-G-': 'BT-120',
'BR-O-': 'BT-120',
'BR-CO-17': 'BT-117', // VAT category tax amount
'BR-CO-18': 'BT-118',
'BR-CO-19': 'BT-116' // VAT category taxable amount
};
for (const [prefix, bt] of Object.entries(btMap)) {
if (ruleId.startsWith(prefix)) {
return bt;
}
}
return undefined;
}
}
/**
* Get VAT category name
*/
export function getVATCategoryName(category: VATCategory): string {
const names: Record<VATCategory, string> = {
[VATCategory.S]: 'Standard rate',
[VATCategory.Z]: 'Zero rated',
[VATCategory.E]: 'Exempt from tax',
[VATCategory.AE]: 'VAT Reverse Charge',
[VATCategory.K]: 'VAT exempt for EEA intra-community supply',
[VATCategory.G]: 'Free export outside EU',
[VATCategory.O]: 'Services outside scope of tax',
[VATCategory.L]: 'Canary Islands general indirect tax',
[VATCategory.M]: 'Tax for production, services and importation in Ceuta and Melilla'
};
return names[category] || 'Unknown';
}