Implemented extractXmlFromPdf(); extracted invoice assertions to InvoiceAsserter

This commit is contained in:
dankito 2024-11-15 00:51:52 +01:00
parent 7ab88390f1
commit 658c02296a
4 changed files with 135 additions and 107 deletions

View File

@ -10,24 +10,35 @@ class EInvoiceReader(
private val mapper: MustangMapper = MustangMapper() private val mapper: MustangMapper = MustangMapper()
) { ) {
fun readFromXml(file: File) = readFromXml(file.inputStream()) fun readFromXml(xmlFile: File) = readFromXml(xmlFile.inputStream())
fun readFromXml(stream: InputStream): Invoice? { fun readFromXml(stream: InputStream) = readFromXml(stream.reader().readText())
fun readFromXml(xml: String): Invoice {
val importer = ZUGFeRDInvoiceImporter() // XRechnungImporter only reads properties but not to a Invoice object val importer = ZUGFeRDInvoiceImporter() // XRechnungImporter only reads properties but not to a Invoice object
importer.fromXML(stream.reader().readText()) importer.fromXML(xml)
return extractInvoice(importer) return extractInvoice(importer)
} }
fun extractFromPdf(file: File) = extractFromPdf(file.inputStream()) fun extractFromPdf(pdfFile: File) = extractFromPdf(pdfFile.inputStream())
fun extractFromPdf(stream: InputStream): Invoice? { fun extractFromPdf(stream: InputStream): Invoice {
val importer = ZUGFeRDInvoiceImporter(stream) val importer = ZUGFeRDInvoiceImporter(stream)
return extractInvoice(importer) return extractInvoice(importer)
} }
private fun extractInvoice(importer: ZUGFeRDInvoiceImporter): Invoice? { fun extractXmlFromPdf(pdfFile: File) = extractXmlFromPdf(pdfFile.inputStream())
fun extractXmlFromPdf(stream: InputStream): String {
val importer = ZUGFeRDInvoiceImporter(stream)
return String(importer.rawXML, Charsets.UTF_8)
}
private fun extractInvoice(importer: ZUGFeRDInvoiceImporter): Invoice {
val invoice = importer.extractInvoice() val invoice = importer.extractInvoice()
// TODO: the values LineTotalAmount, ChargeTotalAmount, AllowanceTotalAmount, TaxBasisTotalAmount, TaxTotalAmount, // TODO: the values LineTotalAmount, ChargeTotalAmount, AllowanceTotalAmount, TaxBasisTotalAmount, TaxTotalAmount,

View File

@ -1,12 +1,9 @@
package net.codinux.invoicing.creation package net.codinux.invoicing.creation
import assertk.assertThat
import assertk.assertions.isNotEmpty
import net.codinux.invoicing.test.DataGenerator import net.codinux.invoicing.test.DataGenerator
import net.codinux.invoicing.test.XPathAsserter import net.codinux.invoicing.test.InvoiceAsserter
import org.mustangproject.ZUGFeRD.ZUGFeRDInvoiceImporter import org.mustangproject.ZUGFeRD.ZUGFeRDInvoiceImporter
import java.io.File import java.io.File
import java.math.BigDecimal
import kotlin.test.Test import kotlin.test.Test
class EInvoiceCreatorTest { class EInvoiceCreatorTest {
@ -49,47 +46,7 @@ class EInvoiceCreatorTest {
private fun createInvoice() = DataGenerator.createInvoice() private fun createInvoice() = DataGenerator.createInvoice()
private fun assertInvoiceXml(xml: String) { private fun assertInvoiceXml(xml: String) {
assertThat(xml).isNotEmpty() InvoiceAsserter.assertInvoiceXml(xml)
val asserter = XPathAsserter(xml)
asserter.xpathHasValue("//rsm:ExchangedDocument/ram:ID", DataGenerator.InvoiceNumber)
asserter.xpathHasValue("//rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString", DataGenerator.InvoicingDate.toString().replace("-", ""))
val senderXPath = "//rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty"
assertParty(asserter, senderXPath, DataGenerator.SenderName, DataGenerator.SenderStreet, DataGenerator.SenderPostalCode, DataGenerator.SenderCity, DataGenerator.SenderVatId, DataGenerator.SenderEmail, DataGenerator.SenderPhone)
val receiverXPath = "//rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:BuyerTradeParty"
assertParty(asserter, receiverXPath, DataGenerator.RecipientName, DataGenerator.RecipientStreet, DataGenerator.RecipientPostalCode, DataGenerator.RecipientCity, DataGenerator.RecipientVatId, DataGenerator.RecipientEmail, DataGenerator.RecipientPhone)
val lineItemXPath = "//rsm:SupplyChainTradeTransaction/ram:IncludedSupplyChainTradeLineItem"
assertLineItem(asserter, lineItemXPath, DataGenerator.ItemName, DataGenerator.ItemUnit, DataGenerator.ItemQuantity, DataGenerator.ItemPrice, DataGenerator.ItemVatPercentage, DataGenerator.ItemDescription)
}
private fun assertParty(asserter: XPathAsserter, partyXPath: String, name: String, street: String, postalCode: String, city: String, vatId: String, email: String, phone: String) {
asserter.xpathHasValue("$partyXPath/ram:Name", name)
asserter.xpathHasValue("$partyXPath/ram:PostalTradeAddress/ram:LineOne", street)
asserter.xpathHasValue("$partyXPath/ram:PostalTradeAddress/ram:PostcodeCode", postalCode)
asserter.xpathHasValue("$partyXPath/ram:PostalTradeAddress/ram:CityName", city)
asserter.xpathHasValue("$partyXPath/ram:SpecifiedTaxRegistration/ram:ID", vatId)
asserter.xpathHasValue("$partyXPath/ram:URIUniversalCommunication/ram:URIID", email)
asserter.xpathHasValue("$partyXPath/ram:DefinedTradeContact/ram:EmailURIUniversalCommunication/ram:URIID", email)
asserter.xpathHasValue("$partyXPath/ram:DefinedTradeContact/ram:TelephoneUniversalCommunication/ram:CompleteNumber", phone)
}
private fun assertLineItem(asserter: XPathAsserter, itemXPath: String, name: String, unit: String, quantity: BigDecimal, price: BigDecimal, vatPercentage: BigDecimal, description: String?) {
asserter.xpathHasValue("$itemXPath/ram:SpecifiedTradeProduct/ram:Name", name)
asserter.xpathHasValue("$itemXPath/ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode", unit)
asserter.xpathHasValue("$itemXPath/ram:SpecifiedLineTradeDelivery/ram:BilledQuantity", quantity, 4)
asserter.xpathHasValue("$itemXPath/ram:SpecifiedLineTradeSettlement/ram:SpecifiedTradeSettlementLineMonetarySummation/ram:LineTotalAmount", price, 2)
asserter.xpathHasValue("$itemXPath/ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent", vatPercentage, 2)
// asserter.xpathHasValue("$partyXPath/ram:URIUniversalCommunication/ram:URIID", description)
} }
} }

View File

@ -1,17 +1,8 @@
package net.codinux.invoicing.reader package net.codinux.invoicing.reader
import assertk.assertThat
import assertk.assertions.hasSize
import assertk.assertions.isEqualTo
import assertk.assertions.isNotNull
import assertk.assertions.isNull
import net.codinux.invoicing.model.BankDetails
import net.codinux.invoicing.model.Invoice import net.codinux.invoicing.model.Invoice
import net.codinux.invoicing.model.LineItem import net.codinux.invoicing.test.InvoiceAsserter
import net.codinux.invoicing.model.Party
import net.codinux.invoicing.test.DataGenerator
import java.io.InputStream import java.io.InputStream
import java.math.BigDecimal
import kotlin.test.Test import kotlin.test.Test
class EInvoiceReaderTest { class EInvoiceReaderTest {
@ -33,57 +24,19 @@ class EInvoiceReaderTest {
assertInvoice(result) assertInvoice(result)
} }
@Test
fun extractXmlFromPdf() {
val result = underTest.extractXmlFromPdf(getTestFile("ZUGFeRD.pdf"))
InvoiceAsserter.assertInvoiceXml(result)
}
private fun getTestFile(filename: String): InputStream = private fun getTestFile(filename: String): InputStream =
this.javaClass.classLoader.getResourceAsStream("files/$filename")!! this.javaClass.classLoader.getResourceAsStream("files/$filename")!!
private fun assertInvoice(invoice: Invoice?) { private fun assertInvoice(invoice: Invoice?) {
assertThat(invoice).isNotNull() InvoiceAsserter.assertInvoice(invoice)
assertThat(invoice!!.invoiceNumber).isEqualTo(DataGenerator.InvoiceNumber)
assertThat(invoice.invoicingDate).isEqualTo(DataGenerator.InvoicingDate)
assertParty(invoice.sender, DataGenerator.SenderName, DataGenerator.SenderStreet, DataGenerator.SenderPostalCode, DataGenerator.SenderCity, DataGenerator.SenderCountry, DataGenerator.SenderVatId, DataGenerator.SenderEmail, DataGenerator.SenderPhone, DataGenerator.SenderBankDetails)
assertParty(invoice.recipient, DataGenerator.RecipientName, DataGenerator.RecipientStreet, DataGenerator.RecipientPostalCode, DataGenerator.RecipientCity, DataGenerator.RecipientCountry, DataGenerator.RecipientVatId, DataGenerator.RecipientEmail, DataGenerator.RecipientPhone, DataGenerator.RecipientBankDetails)
assertThat(invoice.items).hasSize(1)
assertLineItem(invoice.items.first(), DataGenerator.ItemName, DataGenerator.ItemUnit, DataGenerator.ItemQuantity, DataGenerator.ItemPrice, DataGenerator.ItemVatPercentage, DataGenerator.ItemDescription)
}
private fun assertParty(party: Party, name: String, street: String, postalCode: String, city: String, country: String?, vatId: String, email: String, phone: String, bankDetails: BankDetails?) {
assertThat(party.name).isEqualTo(name)
assertThat(party.street).isEqualTo(street)
assertThat(party.postalCode).isEqualTo(postalCode)
assertThat(party.city).isEqualTo(city)
assertThat(party.countryIsoCode).isEqualTo(country)
assertThat(party.vatId).isEqualTo(vatId)
assertThat(party.email).isEqualTo(email)
assertThat(party.phone).isEqualTo(phone)
if (bankDetails == null) {
assertThat(party.bankDetails).isNull()
} else {
assertThat(party.bankDetails!!.accountNumber).isEqualTo(bankDetails.accountNumber)
assertThat(party.bankDetails!!.bankCode).isEqualTo(bankDetails.bankCode)
// due to a bug in Mustang accountName doesn't get extracted from XML, see https://github.com/ZUGFeRD/mustangproject/issues/558
// assertThat(party.bankDetails!!.accountHolderName).isEqualTo(bankDetails.accountHolderName)
}
}
private fun assertLineItem(item: LineItem, name: String, unit: String, quantity: BigDecimal, price: BigDecimal, vatPercentage: BigDecimal, description: String?) {
assertThat(item.name).isEqualTo(name)
assertThat(item.unit).isEqualTo(unit)
assertThat(item.quantity).isEqualTo(quantity.setScale(4))
assertThat(item.price).isEqualTo(price.setScale(4))
assertThat(item.vatPercentage).isEqualTo(vatPercentage.setScale(2))
// assertThat(item.description).isEqualTo(description)
} }
} }

View File

@ -0,0 +1,107 @@
package net.codinux.invoicing.test
import assertk.assertThat
import assertk.assertions.*
import net.codinux.invoicing.model.BankDetails
import net.codinux.invoicing.model.Invoice
import net.codinux.invoicing.model.LineItem
import net.codinux.invoicing.model.Party
import java.math.BigDecimal
object InvoiceAsserter {
fun assertInvoiceXml(xml: String) {
assertThat(xml).isNotEmpty()
val asserter = XPathAsserter(xml)
asserter.xpathHasValue("//rsm:ExchangedDocument/ram:ID", DataGenerator.InvoiceNumber)
asserter.xpathHasValue("//rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString", DataGenerator.InvoicingDate.toString().replace("-", ""))
val senderXPath = "//rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty"
assertParty(asserter, senderXPath, DataGenerator.SenderName, DataGenerator.SenderStreet, DataGenerator.SenderPostalCode, DataGenerator.SenderCity, DataGenerator.SenderVatId, DataGenerator.SenderEmail, DataGenerator.SenderPhone)
val receiverXPath = "//rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:BuyerTradeParty"
assertParty(asserter, receiverXPath, DataGenerator.RecipientName, DataGenerator.RecipientStreet, DataGenerator.RecipientPostalCode, DataGenerator.RecipientCity, DataGenerator.RecipientVatId, DataGenerator.RecipientEmail, DataGenerator.RecipientPhone)
val lineItemXPath = "//rsm:SupplyChainTradeTransaction/ram:IncludedSupplyChainTradeLineItem"
assertLineItem(asserter, lineItemXPath, DataGenerator.ItemName, DataGenerator.ItemUnit, DataGenerator.ItemQuantity, DataGenerator.ItemPrice, DataGenerator.ItemVatPercentage, DataGenerator.ItemDescription)
}
private fun assertParty(asserter: XPathAsserter, partyXPath: String, name: String, street: String, postalCode: String, city: String, vatId: String, email: String, phone: String) {
asserter.xpathHasValue("$partyXPath/ram:Name", name)
asserter.xpathHasValue("$partyXPath/ram:PostalTradeAddress/ram:LineOne", street)
asserter.xpathHasValue("$partyXPath/ram:PostalTradeAddress/ram:PostcodeCode", postalCode)
asserter.xpathHasValue("$partyXPath/ram:PostalTradeAddress/ram:CityName", city)
asserter.xpathHasValue("$partyXPath/ram:SpecifiedTaxRegistration/ram:ID", vatId)
asserter.xpathHasValue("$partyXPath/ram:URIUniversalCommunication/ram:URIID", email)
asserter.xpathHasValue("$partyXPath/ram:DefinedTradeContact/ram:EmailURIUniversalCommunication/ram:URIID", email)
asserter.xpathHasValue("$partyXPath/ram:DefinedTradeContact/ram:TelephoneUniversalCommunication/ram:CompleteNumber", phone)
}
private fun assertLineItem(asserter: XPathAsserter, itemXPath: String, name: String, unit: String, quantity: BigDecimal, price: BigDecimal, vatPercentage: BigDecimal, description: String?) {
asserter.xpathHasValue("$itemXPath/ram:SpecifiedTradeProduct/ram:Name", name)
asserter.xpathHasValue("$itemXPath/ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode", unit)
asserter.xpathHasValue("$itemXPath/ram:SpecifiedLineTradeDelivery/ram:BilledQuantity", quantity, 4)
asserter.xpathHasValue("$itemXPath/ram:SpecifiedLineTradeSettlement/ram:SpecifiedTradeSettlementLineMonetarySummation/ram:LineTotalAmount", price, 2)
asserter.xpathHasValue("$itemXPath/ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent", vatPercentage, 2)
// asserter.xpathHasValue("$partyXPath/ram:URIUniversalCommunication/ram:URIID", description)
}
fun assertInvoice(invoice: Invoice?) {
assertThat(invoice).isNotNull()
assertThat(invoice!!.invoiceNumber).isEqualTo(DataGenerator.InvoiceNumber)
assertThat(invoice.invoicingDate).isEqualTo(DataGenerator.InvoicingDate)
assertParty(invoice.sender, DataGenerator.SenderName, DataGenerator.SenderStreet, DataGenerator.SenderPostalCode, DataGenerator.SenderCity, DataGenerator.SenderCountry, DataGenerator.SenderVatId, DataGenerator.SenderEmail, DataGenerator.SenderPhone, DataGenerator.SenderBankDetails)
assertParty(invoice.recipient, DataGenerator.RecipientName, DataGenerator.RecipientStreet, DataGenerator.RecipientPostalCode, DataGenerator.RecipientCity, DataGenerator.RecipientCountry, DataGenerator.RecipientVatId, DataGenerator.RecipientEmail, DataGenerator.RecipientPhone, DataGenerator.RecipientBankDetails)
assertThat(invoice.items).hasSize(1)
assertLineItem(invoice.items.first(), DataGenerator.ItemName, DataGenerator.ItemUnit, DataGenerator.ItemQuantity, DataGenerator.ItemPrice, DataGenerator.ItemVatPercentage, DataGenerator.ItemDescription)
}
private fun assertParty(party: Party, name: String, street: String, postalCode: String, city: String, country: String?, vatId: String, email: String, phone: String, bankDetails: BankDetails?) {
assertThat(party.name).isEqualTo(name)
assertThat(party.street).isEqualTo(street)
assertThat(party.postalCode).isEqualTo(postalCode)
assertThat(party.city).isEqualTo(city)
assertThat(party.countryIsoCode).isEqualTo(country)
assertThat(party.vatId).isEqualTo(vatId)
assertThat(party.email).isEqualTo(email)
assertThat(party.phone).isEqualTo(phone)
if (bankDetails == null) {
assertThat(party.bankDetails).isNull()
} else {
assertThat(party.bankDetails!!.accountNumber).isEqualTo(bankDetails.accountNumber)
assertThat(party.bankDetails!!.bankCode).isEqualTo(bankDetails.bankCode)
// due to a bug in Mustang accountName doesn't get extracted from XML, see https://github.com/ZUGFeRD/mustangproject/issues/558
// assertThat(party.bankDetails!!.accountHolderName).isEqualTo(bankDetails.accountHolderName)
}
}
private fun assertLineItem(item: LineItem, name: String, unit: String, quantity: BigDecimal, price: BigDecimal, vatPercentage: BigDecimal, description: String?) {
assertThat(item.name).isEqualTo(name)
assertThat(item.unit).isEqualTo(unit)
assertThat(item.quantity).isEqualTo(quantity.setScale(4))
assertThat(item.price).isEqualTo(price.setScale(4))
assertThat(item.vatPercentage).isEqualTo(vatPercentage.setScale(2))
// assertThat(item.description).isEqualTo(description)
}
}