Skip to content

Commit 2194546

Browse files
authored
Merge pull request #7 from ipax77/dev
v3.0.0
2 parents 70e35c7 + a8930a1 commit 2194546

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1863
-388
lines changed

README.md

Lines changed: 86 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33

44
# Introduction
55

6-
`pax.XRechnung.NET` is a .NET library for validating and mapping [XRechnung](https://xeinkauf.de/xrechnung/) XML invoices based on [specification 3.0.2](https://xeinkauf.de/app/uploads/2024/07/302-XRechnung-2024-06-20.pdf).
6+
**pax.XRechnung.NET** is a .NET library that helps validate, map, and generate [XRechnung](https://xeinkauf.de/xrechnung/) XML invoices, following [specification 3.0.2](https://xeinkauf.de/app/uploads/2024/07/302-XRechnung-2024-06-20.pdf).
77

88
## Features
9-
- Validate XRechnung XML invoices
10-
- Map XML invoices to DTOs for easier manipulation
11-
- Generate compliant XML invoices from structured DTOs
9+
-**Validation**: Ensure XML invoices conform to XRechnung 3.0.2 schema and Schematron rules.
10+
- 🔁 **Mapping**: Convert XML invoices into strongly typed DTOs.
11+
- 🧾 **Generation**: Create compliant XML invoices from C# objects.
12+
1213

1314
## Getting started
1415

@@ -20,78 +21,91 @@ dotnet add package pax.XRechnung.NET
2021

2122
## Usage
2223

24+
**Validate XML schema**
25+
```csharp
26+
var xmlText = "<Invoice>...</invoice>";
27+
var serializer = new XmlSerializer(typeof(XmlInvoice));
28+
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(xmlText));
29+
stream.Position = 0;
30+
var xmlInvoice = (XmlInvoice?)serializer.Deserialize(stream);
31+
Assert.IsNotNull(xmlInvoice);
32+
var validationResult = XmlInvoiceValidator.Validate(xmlInvoice);
33+
Assert.IsTrue(validationresult.IsValid);
34+
```
35+
2336
### Handle Sample Invoice
2437
```csharp
25-
public static InvoiceBaseDto GetInvoiceBaseDto()
38+
public static InvoiceBaseDto GetInvoiceBaseDto()
39+
{
40+
return new()
2641
{
27-
return new()
42+
GlobalTaxCategory = "S",
43+
GlobalTaxScheme = "VAT",
44+
GlobalTax = 19.0,
45+
Id = "1",
46+
IssueDate = DateTime.UtcNow,
47+
InvoiceTypeCode = "380",
48+
DocumentCurrencyCode = "EUR",
49+
BuyerReference = "04011000-12345-34",
50+
SellerParty = new PartyBaseDto()
2851
{
29-
GlobalTaxCategory = "S",
30-
GlobalTaxScheme = "VAT",
31-
GlobalTax = 19.0,
32-
Id = "1",
33-
IssueDate = DateTime.UtcNow,
34-
InvoiceTypeCode = "380",
35-
DocumentCurrencyCode = "EUR",
36-
BuyerReference = "04011000-12345-34",
37-
SellerParty = new()
38-
{
39-
Name = "Seller Name",
40-
StreetName = "Test Street",
41-
City = "Test City",
42-
PostCode = "123456",
43-
CountryCode = "DE",
44-
Telefone = "1234/54321",
45-
Email = "seller@example.com",
46-
RegistrationName = "Seller Name",
47-
TaxId = "DE12345678"
48-
},
49-
BuyerParty = new()
50-
{
51-
Name = "Buyer Name",
52-
StreetName = "Test Street",
53-
City = "Test City",
54-
PostCode = "123456",
55-
CountryCode = "DE",
56-
Telefone = "1234/54321",
57-
Email = "buyer@example.com",
58-
RegistrationName = "Buyer Name",
59-
},
60-
PaymentMeans = new()
52+
Name = "Seller Name",
53+
StreetName = "Test Street",
54+
City = "Test City",
55+
PostCode = "123456",
56+
CountryCode = "DE",
57+
Telefone = "1234/54321",
58+
Email = "seller@example.com",
59+
RegistrationName = "Seller Name",
60+
TaxId = "DE12345678"
61+
},
62+
BuyerParty = new PartyBaseDto()
63+
{
64+
Name = "Buyer Name",
65+
StreetName = "Test Street",
66+
City = "Test City",
67+
PostCode = "123456",
68+
CountryCode = "DE",
69+
Telefone = "1234/54321",
70+
Email = "buyer@example.com",
71+
RegistrationName = "Buyer Name",
72+
},
73+
PaymentMeans = new PaymentMeansBaseDto()
74+
{
75+
Iban = "DE12 1234 1234 1234 1234 12",
76+
Bic = "BICABCDE",
77+
Name = "Bank Name"
78+
},
79+
PaymentMeansTypeCode = "30",
80+
PaymentTermsNote = "Zahlbar innerhalb 14 Tagen nach Erhalt der Rechnung.",
81+
PayableAmount = 119.0,
82+
InvoiceLines = [
83+
new InvoiceLineBaseDto()
6184
{
62-
Iban = "DE12 1234 1234 1234 1234 12",
63-
Bic = "BICABCDE",
64-
Name = "Bank Name"
65-
},
66-
PaymentMeansTypeCode = "30",
67-
PaymentTermsNote = "Zahlbar innerhalb 14 Tagen nach Erhalt der Rechnung.",
68-
PayableAmount = 119.0,
69-
InvoiceLines = [
70-
new()
71-
{
72-
Id = "1",
73-
Quantity = 1.0,
74-
QuantityCode = "HUR",
75-
UnitPrice = 100.0,
76-
Name = "Test Job"
77-
}
78-
]
79-
};
80-
}
85+
Id = "1",
86+
Quantity = 1.0,
87+
QuantityCode = "HUR",
88+
UnitPrice = 100.0,
89+
Name = "Test Job"
90+
}
91+
]
92+
};
93+
}
8194
```
82-
**Validate xml schema**
95+
96+
**Serialize DTO to XML**
8397
```csharp
8498
var invoiceBaseDto = GetInvoiceBaseDto();
85-
var mapper = new InvoiceMapper<InvoiceBaseDto>();
99+
var mapper = new InvoiceMapper();
86100
var xmlInvoice = mapper.ToXml(invoiceBaseDto);
87-
var result = XmlInvoiceValidator.Validate(xmlInvoice);
88-
Assert.IsTrue(result.IsValid);
101+
var xmlText = XmlInvoiceWriter.Serialize(xmlInvoice);
89102
```
103+
90104
**Validate schematron - requires [Kosit validator](#java-schematron-validator)**
91105
```csharp
92106
var invoiceBaseDto = GetInvoiceBaseDto();
93-
InvoiceMapper<InvoiceBaseDto> invoiceMapper = new();
94-
XmlInvoice xmlInvoice = invoiceMapper.ToXml(invoiceBaseDto);
107+
var mapper = new InvoiceMapper();
108+
XmlInvoice xmlInvoice = mapper.ToXml(invoiceBaseDto);
95109
var result = await XmlInvoiceValidator.ValidateSchematron(xmlInvoice);
96110
var resultText = string.Join(Environment.NewLine, result.Validations.Select(s => $"{s.Severity}:\t{s.Message}"));
97111
Assert.IsTrue(result.Validations.Count == 0, resultText);
@@ -107,7 +121,15 @@ Server start:
107121

108122
# ChangeLog
109123

110-
<details open="open"><summary>v0.2.0</summary>
124+
<details open="open"><summary>v0.3.0</summary>
125+
126+
>- **Breaking Changes**
127+
>- DTO rework to be more flexible and robust.
128+
>- InvoiceAnnotationDto now available with Required fields and CodeList validation
129+
130+
</details>
131+
132+
<details><summary>v0.2.0</summary>
111133

112134
>- **Breaking Changes**
113135
>- Fixed/Renamed XmlInvoice properties and dependencies. All existing properties are now xml schema conform.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
2+
using System.ComponentModel.DataAnnotations;
3+
using pax.XRechnung.NET.AnnotatedDtos;
4+
using pax.XRechnung.NET.BaseDtos;
5+
6+
namespace pax.XRechnung.NET.tests;
7+
8+
[TestClass]
9+
public class AnnotationDtoTests
10+
{
11+
public static InvoiceAnnotationDto GetInvoiceAnnDto()
12+
{
13+
return new()
14+
{
15+
GlobalTaxCategory = "S",
16+
GlobalTaxScheme = "VAT",
17+
GlobalTax = 19.0,
18+
Id = "1",
19+
IssueDate = DateTime.UtcNow,
20+
InvoiceTypeCode = "380",
21+
DocumentCurrencyCode = "EUR",
22+
BuyerReference = "04011000-12345-34",
23+
SellerParty = new SellerAnnotationDto()
24+
{
25+
Name = "Seller Name",
26+
StreetName = "Test Street",
27+
City = "Test City",
28+
PostCode = "123456",
29+
CountryCode = "DE",
30+
Telefone = "1234/54321",
31+
Email = "seller@example.com",
32+
RegistrationName = "Seller Name",
33+
TaxId = "DE12345678"
34+
},
35+
BuyerParty = new BuyerAnnotationDto()
36+
{
37+
Name = "Buyer Name",
38+
StreetName = "Test Street",
39+
City = "Test City",
40+
PostCode = "123456",
41+
CountryCode = "DE",
42+
Telefone = "1234/54321",
43+
Email = "buyer@example.com",
44+
RegistrationName = "Buyer Name",
45+
},
46+
PaymentMeans = new PaymentAnnotationDto()
47+
{
48+
Iban = "DE12 1234 1234 1234 1234 12",
49+
Bic = "BICABCDE",
50+
Name = "Bank Name"
51+
},
52+
PaymentMeansTypeCode = "30",
53+
PaymentTermsNote = "Zahlbar innerhalb 14 Tagen nach Erhalt der Rechnung.",
54+
PayableAmount = 119.0,
55+
InvoiceLines = [
56+
new InvoiceLineAnnotationDto()
57+
{
58+
Id = "1",
59+
Quantity = 1.0,
60+
QuantityCode = "HUR",
61+
UnitPrice = 100.0,
62+
Name = "Test Job"
63+
}
64+
]
65+
};
66+
}
67+
68+
[TestMethod]
69+
public void InvoiceBaseDtoSchemaIsValidTest()
70+
{
71+
var invoiceAnnDto = GetInvoiceAnnDto();
72+
var mapper = new InvoiceAnnotationMapper();
73+
var xmlInvoice = mapper.ToXml(invoiceAnnDto);
74+
var result = XmlInvoiceValidator.Validate(xmlInvoice);
75+
Assert.IsTrue(result.IsValid);
76+
}
77+
78+
[TestMethod]
79+
public void FromXml_MapsCorrectlyToDto()
80+
{
81+
var xml = SchematronValidationTests.GetStandardXmlInvoice();
82+
var mapper = new InvoiceAnnotationMapper();
83+
84+
var dto = mapper.FromXml(xml);
85+
86+
Assert.AreEqual(xml.Id.Content, dto.Id);
87+
Assert.AreEqual(xml.DocumentCurrencyCode, dto.DocumentCurrencyCode);
88+
Assert.AreEqual(xml.SellerParty.Party.PartyName.Name, dto.SellerParty.Name);
89+
Assert.AreEqual(xml.InvoiceLines.Count, dto.InvoiceLines.Count);
90+
}
91+
92+
[TestMethod]
93+
public void Roundtrip_ProducesEquivalentXml()
94+
{
95+
var original = SchematronValidationTests.GetStandardXmlInvoice();
96+
var mapper = new InvoiceAnnotationMapper();
97+
98+
var dto = mapper.FromXml(original);
99+
var roundtripXml = mapper.ToXml(dto);
100+
101+
Assert.AreEqual(original.Id.Content, roundtripXml.Id.Content);
102+
Assert.AreEqual(original.InvoiceLines.Count, roundtripXml.InvoiceLines.Count);
103+
Assert.AreEqual(original.SellerParty.Party.PartyName.Name, roundtripXml.SellerParty.Party.PartyName.Name);
104+
}
105+
106+
[TestMethod]
107+
public void AnnotationIsValid()
108+
{
109+
var invoiceAnnDto = GetInvoiceAnnDto();
110+
111+
// Validate the DTO properties
112+
var validationContext = new ValidationContext(invoiceAnnDto);
113+
var validationResults = new List<ValidationResult>();
114+
var isValid = Validator.TryValidateObject(invoiceAnnDto, validationContext, validationResults, true);
115+
116+
// Check model validation (ValidCode, Required, etc.)
117+
Assert.IsTrue(isValid, string.Join("; ", validationResults.Select(r => r.ErrorMessage)));
118+
}
119+
120+
[TestMethod]
121+
public void AnnotationIsInValid()
122+
{
123+
var invoiceAnnDto = GetInvoiceAnnDto();
124+
invoiceAnnDto.GlobalTaxCategory = "__InvalidCode__";
125+
126+
// Validate the DTO properties
127+
var validationContext = new ValidationContext(invoiceAnnDto);
128+
var validationResults = new List<ValidationResult>();
129+
var isValid = Validator.TryValidateObject(invoiceAnnDto, validationContext, validationResults, true);
130+
131+
Assert.IsFalse(isValid);
132+
Assert.IsTrue(validationResults.Any());
133+
Assert.AreEqual("The code '__InvalidCode__' is not valid for list 'UNTDID_5305_3'.",
134+
validationResults.FirstOrDefault()?.ErrorMessage);
135+
}
136+
}

0 commit comments

Comments
 (0)