-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathvalidate_vat_notsubject.go
More file actions
198 lines (177 loc) · 8.55 KB
/
Copy pathvalidate_vat_notsubject.go
File metadata and controls
198 lines (177 loc) · 8.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package einvoice
import (
"fmt"
"github.com/shopspring/decimal"
"github.com/speedata/einvoice/rules"
)
// validateVATNotSubject validates BR-O-01 through BR-O-14.
//
// These rules apply to invoices with "Not subject to VAT" category (code 'O').
// This applies to transactions that fall outside the scope of VAT entirely,
// such as certain financial services, insurance, gambling, etc. Unlike VAT
// exemption, these transactions are not part of the VAT system at all.
//
// Key requirements for Not subject to VAT:
// - Must have exactly one VAT breakdown entry with category 'O'
// - Must NOT contain Seller VAT identifier, Seller tax rep VAT identifier, or Buyer VAT identifier
// - Must NOT contain VAT rates (they should be absent, not 0)
// - VAT amount must be 0
// - Must have exemption reason explaining why not subject to VAT
// - All lines/allowances/charges must be category 'O' (cannot mix with other categories)
func (inv *Invoice) validateVATNotSubject() {
// Check if invoice has any "Not subject to VAT" items
hasNotSubjectToVAT := false
for i := range inv.InvoiceLines {
if inv.InvoiceLines[i].TaxCategoryCode == "O" {
hasNotSubjectToVAT = true
break
}
}
if !hasNotSubjectToVAT {
for i := range inv.SpecifiedTradeAllowanceCharge {
if inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxCategoryCode == "O" {
hasNotSubjectToVAT = true
break
}
}
}
// Only validate if invoice uses category O
if !hasNotSubjectToVAT {
return
}
// BR-O-01: Must have exactly one VAT breakdown with category O
oBreakdownCount := 0
var oBreakdown *TradeTax
for i := range inv.TradeTaxes {
if inv.TradeTaxes[i].CategoryCode == "O" {
oBreakdownCount++
oBreakdown = &inv.TradeTaxes[i]
}
}
if oBreakdownCount != 1 {
inv.addViolation(rules.BRO1, fmt.Sprintf("Invoice with 'Not subject to VAT' must have exactly one VAT breakdown with category O (found %d)", oBreakdownCount))
}
// BR-O-02: Invoice lines with category O must NOT contain VAT identifiers
for i := range inv.InvoiceLines {
if inv.InvoiceLines[i].TaxCategoryCode == "O" {
hasVATIdentifiers := inv.Seller.VATaxRegistration != "" ||
inv.Buyer.VATaxRegistration != "" ||
(inv.SellerTaxRepresentativeTradeParty != nil && inv.SellerTaxRepresentativeTradeParty.VATaxRegistration != "")
if hasVATIdentifiers {
inv.addViolation(rules.BRO2, "Invoice line with 'Not subject to VAT' shall not contain Seller VAT (BT-31), Buyer VAT (BT-48), or Seller tax rep VAT (BT-63)")
break
}
}
}
// BR-O-03: Document level allowances with category O must NOT contain VAT identifiers
for i := range inv.SpecifiedTradeAllowanceCharge {
if !inv.SpecifiedTradeAllowanceCharge[i].ChargeIndicator && inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxCategoryCode == "O" {
hasVATIdentifiers := inv.Seller.VATaxRegistration != "" ||
inv.Buyer.VATaxRegistration != "" ||
(inv.SellerTaxRepresentativeTradeParty != nil && inv.SellerTaxRepresentativeTradeParty.VATaxRegistration != "")
if hasVATIdentifiers {
inv.addViolation(rules.BRO3, "Allowance with 'Not subject to VAT' shall not contain Seller VAT (BT-31), Buyer VAT (BT-48), or Seller tax rep VAT (BT-63)")
break
}
}
}
// BR-O-04: Document level charges with category O must NOT contain VAT identifiers
for i := range inv.SpecifiedTradeAllowanceCharge {
if inv.SpecifiedTradeAllowanceCharge[i].ChargeIndicator && inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxCategoryCode == "O" {
hasVATIdentifiers := inv.Seller.VATaxRegistration != "" ||
inv.Buyer.VATaxRegistration != "" ||
(inv.SellerTaxRepresentativeTradeParty != nil && inv.SellerTaxRepresentativeTradeParty.VATaxRegistration != "")
if hasVATIdentifiers {
inv.addViolation(rules.BRO4, "Charge with 'Not subject to VAT' shall not contain Seller VAT (BT-31), Buyer VAT (BT-48), or Seller tax rep VAT (BT-63)")
break
}
}
}
// BR-O-05: Invoice lines with category O must NOT contain VAT rate
for i := range inv.InvoiceLines {
if inv.InvoiceLines[i].TaxCategoryCode != "O" {
continue
}
if !inv.InvoiceLines[i].TaxRateApplicablePercent.IsZero() || inv.InvoiceLines[i].hasTaxRateApplicablePercent {
inv.addViolation(rules.BRO5, "Invoice line with 'Not subject to VAT' shall not contain VAT rate (BT-152)")
}
}
// BR-O-06: Allowances with category O must NOT contain VAT rate
for i := range inv.SpecifiedTradeAllowanceCharge {
if !inv.SpecifiedTradeAllowanceCharge[i].ChargeIndicator && inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxCategoryCode == "O" && !inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxRateApplicablePercent.IsZero() {
inv.addViolation(rules.BRO6, "Allowance with 'Not subject to VAT' shall not contain VAT rate (BT-96)")
}
}
// BR-O-07: Charges with category O must NOT contain VAT rate
for i := range inv.SpecifiedTradeAllowanceCharge {
if inv.SpecifiedTradeAllowanceCharge[i].ChargeIndicator && inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxCategoryCode == "O" && !inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxRateApplicablePercent.IsZero() {
inv.addViolation(rules.BRO7, "Charge with 'Not subject to VAT' shall not contain VAT rate (BT-103)")
}
}
// BR-O-08: VAT category taxable amount calculation
// Note: This validation only applies to profiles with line items (>= Basic, level 3).
// BasicWL profile (level 2) provides BasisAmount directly without line items.
if oBreakdown != nil && (inv.ProfileLevel() >= levelBasic || (inv.ProfileLevel() == 0 && len(inv.InvoiceLines) > 0)) {
var lineTotal decimal.Decimal
for i := range inv.InvoiceLines {
if inv.InvoiceLines[i].TaxCategoryCode == "O" {
lineTotal = lineTotal.Add(inv.InvoiceLines[i].Total)
}
}
var allowanceTotal decimal.Decimal
var chargeTotal decimal.Decimal
for i := range inv.SpecifiedTradeAllowanceCharge {
if inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxCategoryCode == "O" {
if inv.SpecifiedTradeAllowanceCharge[i].ChargeIndicator {
chargeTotal = chargeTotal.Add(inv.SpecifiedTradeAllowanceCharge[i].ActualAmount)
} else {
allowanceTotal = allowanceTotal.Add(inv.SpecifiedTradeAllowanceCharge[i].ActualAmount)
}
}
}
expectedBasis := roundHalfUp(lineTotal.Sub(allowanceTotal).Add(chargeTotal), 2)
if !oBreakdown.BasisAmount.Equal(expectedBasis) {
inv.addViolation(rules.BRO8, fmt.Sprintf("'Not subject to VAT' taxable amount mismatch: got %s, expected %s", oBreakdown.BasisAmount.StringFixed(2), expectedBasis.StringFixed(2)))
}
}
// BR-O-09: VAT amount must be 0
if oBreakdown != nil && !oBreakdown.CalculatedAmount.IsZero() {
inv.addViolation(rules.BRO9, "'Not subject to VAT' tax amount must be 0")
}
// BR-O-10: Must have exemption reason
if oBreakdown != nil && oBreakdown.ExemptionReason == "" && oBreakdown.ExemptionReasonCode == "" {
inv.addViolation(rules.BRO10, "'Not subject to VAT' breakdown must have exemption reason (BT-120) or reason code (BT-121)")
}
// BR-O-11: Only one VAT breakdown allowed (already checked in BR-O-01)
// This rule means if you have category O, you cannot have any other categories
if oBreakdownCount == 1 && len(inv.TradeTaxes) > 1 {
inv.addViolation(rules.BRO11, "Invoice with 'Not subject to VAT' breakdown shall not contain other VAT categories")
}
// BR-O-12: All invoice lines must be category O
if oBreakdownCount > 0 {
for i := range inv.InvoiceLines {
if inv.InvoiceLines[i].TaxCategoryCode != "O" {
inv.addViolation(rules.BRO12, fmt.Sprintf("Invoice with 'Not subject to VAT' breakdown shall not contain invoice lines with other categories (found %s)", inv.InvoiceLines[i].TaxCategoryCode))
break
}
}
}
// BR-O-13: All allowances must be category O
if oBreakdownCount > 0 {
for i := range inv.SpecifiedTradeAllowanceCharge {
if !inv.SpecifiedTradeAllowanceCharge[i].ChargeIndicator && inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxCategoryCode != "O" {
inv.addViolation(rules.BRO13, fmt.Sprintf("Invoice with 'Not subject to VAT' breakdown shall not contain allowances with other categories (found %s)", inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxCategoryCode))
break
}
}
}
// BR-O-14: All charges must be category O
if oBreakdownCount > 0 {
for i := range inv.SpecifiedTradeAllowanceCharge {
if inv.SpecifiedTradeAllowanceCharge[i].ChargeIndicator && inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxCategoryCode != "O" {
inv.addViolation(rules.BRO14, fmt.Sprintf("Invoice with 'Not subject to VAT' breakdown shall not contain charges with other categories (found %s)", inv.SpecifiedTradeAllowanceCharge[i].CategoryTradeTaxCategoryCode))
break
}
}
}
}