Skip to content

Commit a1ad3e4

Browse files
committed
Added ValidationResult for more meaningful validation
closes #692
1 parent cb56717 commit a1ad3e4

File tree

2 files changed

+98
-43
lines changed

2 files changed

+98
-43
lines changed

ZUGFeRD/InvoiceValidator.cs

+68-43
Original file line numberDiff line numberDiff line change
@@ -35,32 +35,37 @@ public class InvoiceValidator
3535
{
3636
public static void ValidateAndPrint(InvoiceDescriptor descriptor, ZUGFeRDVersion version, string filename = null)
3737
{
38-
List<string> output = Validate(descriptor, version);
38+
ValidationResult validationResult = Validate(descriptor, version);
3939

4040
if (!String.IsNullOrWhiteSpace(filename))
4141
{
42-
System.IO.File.WriteAllText(filename, string.Join("\n", output));
42+
System.IO.File.WriteAllText(filename, string.Join("\n", validationResult.Messages));
4343
}
4444

45-
foreach (string line in output)
45+
foreach (string line in validationResult.Messages)
4646
{
4747
System.Console.WriteLine(line);
4848
}
4949
} // !ValidateAndPrint()
5050

5151

52-
public static List<string> Validate(InvoiceDescriptor descriptor, ZUGFeRDVersion version)
52+
public static ValidationResult Validate(InvoiceDescriptor descriptor, ZUGFeRDVersion version)
5353
{
54-
List<string> retval = new List<string>();
54+
ValidationResult retval = new ValidationResult()
55+
{
56+
IsValid = true
57+
};
58+
5559
if (descriptor == null)
5660
{
57-
retval.Add("Invalid invoice descriptor");
61+
retval.Messages.Add("Invalid invoice descriptor");
62+
retval.IsValid = false;
5863
return retval;
5964
}
6065

6166
// line item summation
62-
retval.Add("Validating invoice monetary summation");
63-
retval.Add(String.Format("Starting recalculating line total from {0} items...", descriptor.GetTradeLineItems().Count));
67+
retval.Messages.Add("Validating invoice monetary summation");
68+
retval.Messages.Add(String.Format("Starting recalculating line total from {0} items...", descriptor.GetTradeLineItems().Count));
6469
int lineCounter = 0;
6570

6671
decimal lineTotal = 0m;
@@ -90,18 +95,18 @@ public static List<string> Validate(InvoiceDescriptor descriptor, ZUGFeRDVersion
9095
retval.Add(String.Format("Current monetarySummation.lineTotal = {0:0.0000} EUR(the sum of all line totals)", lineTotal));
9196
*/
9297

93-
retval.Add(String.Format("{0};{1};{2}", ++lineCounter, item.Name, total));
98+
retval.Messages.Add(String.Format("{0};{1};{2}", ++lineCounter, item.Name, total));
9499
}
95100

96-
retval.Add("==> DONE!");
97-
retval.Add("Finished recalculating monetarySummation.lineTotal...");
98-
retval.Add("Adding tax amounts from invoice allowance charge...");
101+
retval.Messages.Add("==> DONE!");
102+
retval.Messages.Add("Finished recalculating monetarySummation.lineTotal...");
103+
retval.Messages.Add("Adding tax amounts from invoice allowance charge...");
99104

100105
decimal allowanceTotal = 0.0m;
101106
decimal chargeTotal = 0.0m;
102107
foreach (TradeAllowanceCharge charge in descriptor.GetTradeAllowanceCharges())
103108
{
104-
retval.Add(String.Format("==> added {0:0.00} to {1:0.00}%", -charge.Amount, charge.Tax.Percent));
109+
retval.Messages.Add(String.Format("==> added {0:0.00} to {1:0.00}%", -charge.Amount, charge.Tax.Percent));
105110

106111
if (!lineTotalPerTax.ContainsKey(charge.Tax.Percent))
107112
{
@@ -119,28 +124,28 @@ public static List<string> Validate(InvoiceDescriptor descriptor, ZUGFeRDVersion
119124
}
120125
}
121126

122-
retval.Add("Adding tax amounts from invoice service charge...");
127+
retval.Messages.Add("Adding tax amounts from invoice service charge...");
123128
// TODO
124129

125130
// TODO ausgeben: Recalculating tax basis for tax percentages: [Key{percentage=7.00, code=[VAT] Value added tax, category=[S] Standard rate}, Key{percentage=19.00, code=[VAT] Value added tax, category=[S] Standard rate}]
126-
retval.Add(String.Format("Recalculated tax basis = {0:0.0000}", lineTotal - allowanceTotal));
127-
retval.Add("Calculating tax total...");
131+
retval.Messages.Add(String.Format("Recalculated tax basis = {0:0.0000}", lineTotal - allowanceTotal));
132+
retval.Messages.Add("Calculating tax total...");
128133

129134
decimal taxTotal = 0.0m;
130135
foreach (KeyValuePair<decimal, decimal> kv in lineTotalPerTax)
131136
{
132137
decimal taxTotalForLine = Decimal.Divide(Decimal.Multiply(kv.Value, kv.Key), 100.0m);
133138
taxTotal += taxTotalForLine;
134-
retval.Add(String.Format("===> {0:0.0000} x {1:0.00}% = {2:0.00}", kv.Value, kv.Key, taxTotalForLine));
139+
retval.Messages.Add(String.Format("===> {0:0.0000} x {1:0.00}% = {2:0.00}", kv.Value, kv.Key, taxTotalForLine));
135140
}
136141

137142
decimal grandTotal = lineTotal - allowanceTotal + taxTotal + chargeTotal;
138143
decimal prepaid = 0m; // TODO: calculcate
139144

140-
retval.Add(String.Format("Recalculated tax total = {0:0.00}", taxTotal));
141-
retval.Add(String.Format("Recalculated grand total = {0:0.0000} EUR(tax basis total + tax total)", grandTotal));
142-
retval.Add("Recalculating invoice monetary summation DONE!");
143-
retval.Add(String.Format("==> result: MonetarySummation[lineTotal = {0:0.0000} EUR, chargeTotal = {1:0.0000} EUR, allowanceTotal = {2:0.0000} EUR, taxBasisTotal = {3:0.0000} EUR, taxTotal = {4:0.0000} EUR, grandTotal = {5:0.0000} EUR, totalPrepaid = {6:0.0000} EUR, duePayable = {7:0.0000} EUR]",
145+
retval.Messages.Add(String.Format("Recalculated tax total = {0:0.00}", taxTotal));
146+
retval.Messages.Add(String.Format("Recalculated grand total = {0:0.0000} EUR(tax basis total + tax total)", grandTotal));
147+
retval.Messages.Add("Recalculating invoice monetary summation DONE!");
148+
retval.Messages.Add(String.Format("==> result: MonetarySummation[lineTotal = {0:0.0000} EUR, chargeTotal = {1:0.0000} EUR, allowanceTotal = {2:0.0000} EUR, taxBasisTotal = {3:0.0000} EUR, taxTotal = {4:0.0000} EUR, grandTotal = {5:0.0000} EUR, totalPrepaid = {6:0.0000} EUR, duePayable = {7:0.0000} EUR]",
144149
lineTotal,
145150
chargeTotal,
146151
allowanceTotal,
@@ -174,71 +179,79 @@ public static List<string> Validate(InvoiceDescriptor descriptor, ZUGFeRDVersion
174179

175180
if (!descriptor.TaxTotalAmount.HasValue)
176181
{
177-
retval.Add(String.Format("trade.settlement.monetarySummation.taxTotal Message: Kein TaxTotalAmount vorhanden"));
182+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.taxTotal Message: Kein TaxTotalAmount vorhanden"));
183+
retval.IsValid = false;
178184
}
179185
else if (Math.Abs(taxTotal - descriptor.TaxTotalAmount.Value) < 0.01m)
180186
{
181-
retval.Add(String.Format("trade.settlement.monetarySummation.taxTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", taxTotal));
187+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.taxTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", taxTotal));
182188
}
183189
else
184190
{
185-
retval.Add(String.Format("trade.settlement.monetarySummation.taxTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", taxTotal, descriptor.TaxTotalAmount));
191+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.taxTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", taxTotal, descriptor.TaxTotalAmount));
192+
retval.IsValid = false;
186193
}
187194

188195
if (Math.Abs(lineTotal - descriptor.LineTotalAmount.Value) < 0.01m)
189196
{
190-
retval.Add(String.Format("trade.settlement.monetarySummation.lineTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", lineTotal));
197+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.lineTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", lineTotal));
191198
}
192199
else
193200
{
194-
retval.Add(String.Format("trade.settlement.monetarySummation.lineTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", lineTotal, descriptor.LineTotalAmount));
201+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.lineTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", lineTotal, descriptor.LineTotalAmount));
202+
retval.IsValid = false;
195203
}
196204

197205
if (!descriptor.GrandTotalAmount.HasValue)
198206
{
199-
retval.Add(String.Format("trade.settlement.monetarySummation.grandTotal Message: Kein GrandTotalAmount vorhanden"));
207+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.grandTotal Message: Kein GrandTotalAmount vorhanden"));
208+
retval.IsValid = false;
200209
}
201210
else if (Math.Abs(grandTotal - descriptor.GrandTotalAmount.Value) < 0.01m)
202211
{
203-
retval.Add(String.Format("trade.settlement.monetarySummation.grandTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", grandTotal));
212+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.grandTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", grandTotal));
204213
}
205214
else
206215
{
207-
retval.Add(String.Format("trade.settlement.monetarySummation.grandTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", grandTotal, descriptor.GrandTotalAmount));
216+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.grandTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", grandTotal, descriptor.GrandTotalAmount));
217+
retval.IsValid = false;
208218
}
209219

210220
/*
211221
* @todo Richtige Validierung implementieren
212222
*/
213223
if (Math.Abs(taxBasisTotal - taxBasisTotal) < 0.01m)
214224
{
215-
retval.Add(String.Format("trade.settlement.monetarySummation.taxBasisTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", taxBasisTotal));
225+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.taxBasisTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", taxBasisTotal));
216226
}
217227
else
218228
{
219-
retval.Add(String.Format("trade.settlement.monetarySummation.taxBasisTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", taxBasisTotal, taxBasisTotal));
229+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.taxBasisTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", taxBasisTotal, taxBasisTotal));
230+
retval.IsValid = false;
220231
}
221232

222233
if (Math.Abs(allowanceTotalSummedPerTradeAllowanceCharge - allowanceTotal) < 0.01m)
223234
{
224-
retval.Add(String.Format("trade.settlement.monetarySummation.allowanceTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", allowanceTotalSummedPerTradeAllowanceCharge));
235+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.allowanceTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", allowanceTotalSummedPerTradeAllowanceCharge));
225236
}
226237
else
227238
{
228-
retval.Add(String.Format("trade.settlement.monetarySummation.allowanceTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", allowanceTotalSummedPerTradeAllowanceCharge, allowanceTotal));
239+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.allowanceTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", allowanceTotalSummedPerTradeAllowanceCharge, allowanceTotal));
240+
retval.IsValid = false;
229241
}
230242

231243
if (Math.Abs(chargesTotalSummedPerTradeAllowanceCharge - chargeTotal) < 0.01m)
232244
{
233-
retval.Add(String.Format("trade.settlement.monetarySummation.chargeTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", chargesTotalSummedPerTradeAllowanceCharge));
245+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.chargeTotal Message: Berechneter Wert ist wie vorhanden:[{0:0.0000}]", chargesTotalSummedPerTradeAllowanceCharge));
234246
}
235247
else
236248
{
237-
retval.Add(String.Format("trade.settlement.monetarySummation.chargeTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", chargesTotalSummedPerTradeAllowanceCharge, chargeTotal));
249+
retval.Messages.Add(String.Format("trade.settlement.monetarySummation.chargeTotal Message: Berechneter Wert ist[{0:0.0000}] aber tatsächliche vorhander Wert ist[{1:0.0000}] | Actual value: {1:0.0000})", chargesTotalSummedPerTradeAllowanceCharge, chargeTotal));
250+
retval.IsValid = false;
238251
}
239252

240253
// version-specific validation
241-
List<string> versionSpecificResults = new List<string>();
254+
ValidationResult versionSpecificResults = new ValidationResult();
242255
switch (version)
243256
{
244257
case ZUGFeRDVersion.Version1:
@@ -252,35 +265,47 @@ public static List<string> Validate(InvoiceDescriptor descriptor, ZUGFeRDVersion
252265
}
253266
}
254267

268+
if (versionSpecificResults.IsValid == false)
269+
{
270+
retval.IsValid = false;
271+
}
272+
retval.Messages.AddRange(versionSpecificResults.Messages);
255273

256274
return retval;
257275
} // !Validate()
258276

259277

260-
private static List<string> _ValidateAccordingToVersion1(InvoiceDescriptor descriptor)
278+
private static ValidationResult _ValidateAccordingToVersion1(InvoiceDescriptor descriptor)
261279
{
262-
List<string> retval = new List<string>();
280+
ValidationResult retval = new ValidationResult()
281+
{
282+
IsValid = true
283+
};
263284

264285
if (!EnumExtensions.In<GlobalIDSchemeIdentifiers>(descriptor.Buyer?.GlobalID?.SchemeID, GlobalIDSchemeIdentifiers.Swift, GlobalIDSchemeIdentifiers.DUNS, GlobalIDSchemeIdentifiers.GLN, GlobalIDSchemeIdentifiers.EAN, GlobalIDSchemeIdentifiers.ODETTE))
265286
{
266-
retval.Add($"Global identifier scheme {descriptor.Buyer?.GlobalID?.SchemeID} is not supported for buyers in ZUGFeRD 1.0");
287+
retval.IsValid = false;
288+
retval.Messages.Add($"Global identifier scheme {descriptor.Buyer?.GlobalID?.SchemeID} is not supported for buyers in ZUGFeRD 1.0");
267289
}
268290

269291
if (!EnumExtensions.In<GlobalIDSchemeIdentifiers>(descriptor.Seller?.GlobalID?.SchemeID, GlobalIDSchemeIdentifiers.Swift, GlobalIDSchemeIdentifiers.DUNS, GlobalIDSchemeIdentifiers.GLN, GlobalIDSchemeIdentifiers.EAN, GlobalIDSchemeIdentifiers.ODETTE))
270292
{
271-
retval.Add($"Global identifier scheme {descriptor.Buyer?.GlobalID?.SchemeID} is not supported for sellers in ZUGFeRD 1.0");
293+
retval.IsValid = false;
294+
retval.Messages.Add($"Global identifier scheme {descriptor.Buyer?.GlobalID?.SchemeID} is not supported for sellers in ZUGFeRD 1.0");
272295
}
273296

274297
if (!EnumExtensions.In<GlobalIDSchemeIdentifiers>(descriptor.ShipFrom?.GlobalID?.SchemeID, GlobalIDSchemeIdentifiers.Swift, GlobalIDSchemeIdentifiers.DUNS, GlobalIDSchemeIdentifiers.GLN, GlobalIDSchemeIdentifiers.EAN, GlobalIDSchemeIdentifiers.ODETTE))
275298
{
276-
retval.Add($"Global identifier scheme {descriptor.Buyer?.GlobalID?.SchemeID} is not supported for senders (ShipFrom) in ZUGFeRD 1.0");
299+
retval.IsValid = false;
300+
retval.Messages.Add($"Global identifier scheme {descriptor.Buyer?.GlobalID?.SchemeID} is not supported for senders (ShipFrom) in ZUGFeRD 1.0");
277301
}
278302

279303
if (!EnumExtensions.In<GlobalIDSchemeIdentifiers>(descriptor.ShipTo?.GlobalID?.SchemeID, GlobalIDSchemeIdentifiers.Swift, GlobalIDSchemeIdentifiers.DUNS, GlobalIDSchemeIdentifiers.GLN, GlobalIDSchemeIdentifiers.EAN, GlobalIDSchemeIdentifiers.ODETTE))
280304
{
281-
retval.Add($"Global identifier scheme {descriptor.Buyer?.GlobalID?.SchemeID} is not supported for recipients (ShipTo) in ZUGFeRD 1.0");
305+
retval.IsValid = false;
306+
retval.Messages.Add($"Global identifier scheme {descriptor.Buyer?.GlobalID?.SchemeID} is not supported for recipients (ShipTo) in ZUGFeRD 1.0");
282307
}
283-
308+
284309
return retval;
285310
} // !_ValidateAccordingToVersion1()
286311
}

ZUGFeRD/ValidationResult.cs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
using System;
20+
using System.Collections.Generic;
21+
using System.Text;
22+
23+
namespace s2industries.ZUGFeRD
24+
{
25+
public class ValidationResult
26+
{
27+
public bool IsValid { get; set; } = false;
28+
public List<string> Messages { get; set; } = new List<string>();
29+
}
30+
}

0 commit comments

Comments
 (0)