Compare commits

...

2 Commits

8 changed files with 192 additions and 0 deletions

View File

@ -29,6 +29,18 @@ val mailsWithEInvoices = mailReader.listAllMessagesWithEInvoice(MailAccount(
))
```
### Validate eInvoice
```kotlin
val validator = EInvoiceValidator()
val invoiceFile = File("ZUGFeRD.pdf") // or XRechnung,xml, ...
val result = validator.validate(invoiceFile)
println("Is valid? ${result.isValid}")
println(result.report)
```
## Create eInvoice
```kotlin

View File

@ -20,6 +20,7 @@ val logbackVersion: String by project
dependencies {
implementation("org.mustangproject:library:$mustangVersion")
implementation("org.mustangproject:validator:$mustangVersion")
implementation("org.eclipse.angus:angus-mail:$angusMailVersion")

View File

@ -0,0 +1,55 @@
package net.codinux.invoicing.validation
import org.mustangproject.validator.ZUGFeRDValidator
import java.io.File
import java.lang.reflect.Field
class EInvoiceValidator {
companion object {
private val SectionField = getPrivateField("section")
private val CriterionField = getPrivateField("criterion")
private val StacktraceField = getPrivateField("stacktrace")
private fun getPrivateField(fieldName: String): Field? = try {
org.mustangproject.validator.ValidationResultItem::class.java.getDeclaredField(fieldName).apply {
trySetAccessible()
}
} catch (e: Throwable) {
null
}
}
fun validate(fileToValidate: File, disableNotices: Boolean = false): InvoiceValidationResult {
val validator = object : ZUGFeRDValidator() {
fun getContext() = this.context
}
if (disableNotices) {
validator.disableNotices()
}
val report = validator.validate(fileToValidate.absolutePath)
val context = validator.getContext()
val isXmlValid = context.isValid
val xmlValidationResults = context.results.map { mapValidationResultItem(it) }
// TODO: currently it's not possible to get PDF validation result as for PDF validation the same context object
// is used and then in a private method before XML validation context.clear() gets called removing all PDF validation results
return InvoiceValidationResult(validator.wasCompletelyValid(), isXmlValid, xmlValidationResults, report)
}
private fun mapValidationResultItem(item: org.mustangproject.validator.ValidationResultItem) =
ValidationResultItem(mapSeverity(item), item.message, item.location, SectionField?.get(item) as? Int, CriterionField?.get(item) as? String, StacktraceField?.get(item) as? String)
private fun mapSeverity(item: org.mustangproject.validator.ValidationResultItem): ValidationResultSeverity {
var name = item.severity.name
name = name.first().uppercase() + name.substring(1)
return ValidationResultSeverity.valueOf(name)
}
}

View File

@ -0,0 +1,31 @@
package net.codinux.invoicing.validation
class InvoiceValidationResult(
/**
* If XML and, if supplied, PDF is valid.
*/
val isValid: Boolean,
/**
* If eInvoice XML is valid. If PDF is invalid, then [isValid] is false, but [isXmlValid] still can be true.
*/
val isXmlValid: Boolean,
/**
*
*/
val xmlValidationResults: List<ValidationResultItem>,
/**
* The validation report as a custom XML.
*/
val reportAsXml: String
) {
val countXmlNotices: Int by lazy { xmlValidationResults.count { it.severity == ValidationResultSeverity.Notice } }
val countXmlErrors: Int by lazy { xmlValidationResults.count { it.severity == ValidationResultSeverity.Error } }
val countXmlFatalOrExcepton: Int by lazy { xmlValidationResults.count { it.severity == ValidationResultSeverity.Fatal || it.severity == ValidationResultSeverity.Exception } }
override fun toString() = when (isValid) {
true -> "Valid: $reportAsXml"
false -> "Invalid: $reportAsXml"
}
}

View File

@ -0,0 +1,12 @@
package net.codinux.invoicing.validation
class ValidationResultItem(
val severity: ValidationResultSeverity,
val message: String,
val location: String?,
val section: Int?,
val criterion: String?,
val stacktrace: String? = null
) {
override fun toString() = "$severity: $message"
}

View File

@ -0,0 +1,9 @@
package net.codinux.invoicing.validation
enum class ValidationResultSeverity {
Notice,
Warning,
Error,
Fatal,
Exception
}

View File

@ -7,6 +7,7 @@ import net.codinux.invoicing.model.Invoice
import net.codinux.invoicing.model.LineItem
import net.codinux.invoicing.model.Party
import net.codinux.invoicing.reader.EInvoiceReader
import net.codinux.invoicing.validation.EInvoiceValidator
import java.io.File
import java.math.BigDecimal
import java.time.LocalDate
@ -34,6 +35,17 @@ class Demonstration {
))
}
fun validate() {
val validator = EInvoiceValidator()
val invoiceFile = File("ZUGFeRD.pdf") // or XRechnung,xml, ...
val result = validator.validate(invoiceFile)
println("Is valid? ${result.isValid}")
println(result.reportAsXml)
}
fun create() {
val invoice = createInvoice()
val pdfResultFile = File.createTempFile("Zugferd", ".pdf")

View File

@ -0,0 +1,60 @@
package net.codinux.invoicing.validation
import assertk.assertThat
import assertk.assertions.*
import java.io.File
import kotlin.test.Test
class EInvoiceValidatorTest {
private val underTest = EInvoiceValidator()
@Test
fun validateXRechnung() {
val testFile = getTestFile("XRechnung.xml")
val result = underTest.validate(testFile)
assertThat(result.isValid).isFalse() // TODO: add required properties to XRechnung.xml
assertThat(result.reportAsXml).isNotEmpty()
assertThat(result.xmlValidationResults).hasSize(3)
assertThat(result.countXmlNotices).isEqualTo(0)
assertThat(result.countXmlErrors).isEqualTo(3)
assertThat(result.countXmlFatalOrExcepton).isEqualTo(0)
}
@Test
fun validateZugferdPdf() {
val testFile = getTestFile("ZUGFeRD.pdf")
val result = underTest.validate(testFile)
assertThat(result.isValid).isTrue()
assertThat(result.reportAsXml).isNotEmpty()
assertThat(result.xmlValidationResults).hasSize(5)
assertThat(result.countXmlNotices).isEqualTo(5)
assertThat(result.countXmlErrors).isEqualTo(0)
assertThat(result.countXmlFatalOrExcepton).isEqualTo(0)
}
@Test
fun validateZugferdXml() {
val testFile = getTestFile("ZUGFeRD.xml")
val result = underTest.validate(testFile)
assertThat(result.isValid).isTrue()
assertThat(result.reportAsXml).isNotEmpty()
assertThat(result.xmlValidationResults).hasSize(5)
assertThat(result.countXmlNotices).isEqualTo(5)
assertThat(result.countXmlErrors).isEqualTo(0)
assertThat(result.countXmlFatalOrExcepton).isEqualTo(0)
}
private fun getTestFile(filename: String): File =
File(this.javaClass.classLoader.getResource("files/$filename")!!.toURI())
}