Implemented creating XRechnung

This commit is contained in:
dankito 2024-11-13 19:58:06 +01:00
parent 4293c8a7bb
commit 3273625f7a
9 changed files with 212 additions and 1 deletions

View File

@ -0,0 +1,31 @@
plugins {
kotlin("jvm")
}
kotlin {
jvmToolchain(11)
}
val mustangVersion: String by project
val klfVersion: String by project
val assertKVersion: String by project
dependencies {
implementation("org.mustangproject:library:$mustangVersion")
implementation("net.codinux.log:klf:$klfVersion")
testImplementation(kotlin("test"))
implementation("com.willowtreeapps.assertk:assertk:$assertKVersion")
}
tasks.test {
useJUnitPlatform()
}

View File

@ -0,0 +1,19 @@
package net.codinux.invoicing.creation
import net.codinux.invoicing.model.Invoice
import org.mustangproject.ZUGFeRD.Profiles
import org.mustangproject.ZUGFeRD.ZUGFeRD2PullProvider
class EInvoiceCreator(
private val mapper: MustangMapper = MustangMapper()
) {
fun createXRechnungXml(invoice: Invoice): String {
val provider = ZUGFeRD2PullProvider()
provider.profile = Profiles.getByName("XRechnung")
provider.generateXML(mapper.mapToTransaction(invoice))
return String(provider.xml, Charsets.UTF_8)
}
}

View File

@ -0,0 +1,57 @@
package net.codinux.invoicing.creation
import net.codinux.invoicing.model.LineItem
import org.mustangproject.Invoice
import org.mustangproject.Item
import org.mustangproject.Product
import org.mustangproject.TradeParty
import org.mustangproject.ZUGFeRD.IExportableTransaction
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.util.*
class MustangMapper {
fun mapToTransaction(invoice: net.codinux.invoicing.model.Invoice): IExportableTransaction = Invoice().apply {
this.number = invoice.invoiceNumber
this.issueDate = map(invoice.invoicingDate)
this.sender = mapParty(invoice.sender)
this.recipient = mapParty(invoice.recipient)
this.setZFItems(ArrayList(invoice.items.map { mapLineItem(it) }))
this.dueDate = map(invoice.dueDate)
}
fun mapParty(party: net.codinux.invoicing.model.Party): TradeParty = TradeParty(
party.name, "${party.streetName} ${party.houseNumber}", party.postalCode, party.city, party.country
).apply {
this.taxID = party.taxNumber
// TODO: vatID?
// TODO: ID?
// TODO: description?
this.email = party.email
}
fun mapLineItem(item: LineItem): IZUGFeRDExportableItem = Item(
// description has to be an empty string if not set
Product(item.name, item.description ?: "", item.unit, item.vatPercentage), item.price, item.quantity
).apply {
}
@JvmName("mapNullable")
private fun map(date: LocalDate?) =
date?.let { map(it) }
private fun map(date: LocalDate): Date =
Date.from(mapToInstant(date))
private fun mapToInstant(date: LocalDate): Instant =
date.atStartOfDay(ZoneId.systemDefault()).toInstant()
}

View File

@ -0,0 +1,14 @@
package net.codinux.invoicing.model
import java.time.LocalDate
class Invoice(
val invoiceNumber: String,
val invoicingDate: LocalDate,
val sender: Party,
val recipient: Party,
val items: List<LineItem>,
val dueDate: LocalDate? = null,
) {
override fun toString() = "$invoicingDate $invoiceNumber to $recipient"
}

View File

@ -0,0 +1,14 @@
package net.codinux.invoicing.model
import java.math.BigDecimal
class LineItem(
val name: String,
val unit: String,
val quantity: BigDecimal,
val price: BigDecimal,
val vatPercentage: BigDecimal,
val description: String? = null,
) {
override fun toString() = "$name, $quantity x $price, $vatPercentage %"
}

View File

@ -0,0 +1,19 @@
package net.codinux.invoicing.model
class Party(
val name: String,
val streetName: String,
val houseNumber: String,
var postalCode: String?,
val city: String,
val country: String? = null,
val taxNumber: String? = null, // better name like vatTaxNumber?
val email: String? = null,
// var telephoneNumber: String? = null, // simply telephone?
// var website: String? = null,
) {
override fun toString() = "$name, $city"
}

View File

@ -0,0 +1,56 @@
package net.codinux.invoicing.creation
import assertk.assertThat
import assertk.assertions.isNotEmpty
import net.codinux.invoicing.model.Invoice
import net.codinux.invoicing.model.LineItem
import net.codinux.invoicing.model.Party
import java.math.BigDecimal
import java.time.LocalDate
import kotlin.test.Test
class EInvoiceCreatorTest {
private val underTest = EInvoiceCreator()
@Test
fun createXRechnung() {
val invoice = createInvoice()
val result = underTest.createXRechnungXml(invoice)
assertThat(result).isNotEmpty()
}
private fun createInvoice(
invoiceNumber: String = "12345",
invoicingDate: LocalDate = LocalDate.of(2015, 10, 21),
sender: Party = createParty("Hochwürdiger Leistungserbringer"),
recipient: Party = createParty("Untertänigster Leistungsempfänger"),
items: List<LineItem> = listOf(createItem()),
dueDate: LocalDate? = null
) = Invoice(invoiceNumber, invoicingDate, sender, recipient, items, dueDate)
private fun createParty(
name: String,
streetName: String = "Fun Street",
houseNumber: String = "1",
postalCode: String = "12345",
city: String = "Glückstadt",
country: String? = null,
taxNumber: String? = "DE12345678",
email: String? = null,
) = Party(name, streetName, houseNumber, postalCode, city, country, taxNumber, email)
private fun createItem(
name: String = "Erbrachte Dienstleistungen",
unit: String = "",
quantity: BigDecimal = BigDecimal(1),
price: BigDecimal = BigDecimal(99),
vatPercentage: BigDecimal = BigDecimal(0.19),
description: String? = null,
) = LineItem(name, unit, quantity, price, vatPercentage, description)
}

View File

@ -5,7 +5,7 @@ org.gradle.parallel=true
kotlinVersion=1.9.25 kotlinVersion=1.9.25
coroutinesVersion=1.8.1 mustangVersion=2.14.2
klfVersion=1.6.2 klfVersion=1.6.2

View File

@ -23,3 +23,4 @@ plugins {
rootProject.name = "eInvoicing" rootProject.name = "eInvoicing"
include("e-invoicing-domain")