Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies {
implementation("org.springdoc:springdoc-openapi-kotlin:1.6.11")

implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("com.google.code.gson:gson:2.10.1")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
runtimeOnly("org.postgresql:postgresql")
Expand Down
8 changes: 7 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ services:
ports:
- "5432:5432"
service:
build: .
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
links:
- database
- traefik
Expand All @@ -36,6 +40,8 @@ services:
env_file:
- database.env
- service.env
environment:
- DATABASE_URL=jdbc:postgresql://database:5432/
labels:
- "traefik.enable=true"
- "traefik.http.routers.organisations.rule=PathPrefix(`/swagger-ui`) || PathPrefix(`/organisations`) || PathPrefix(`/countries`)"
Expand Down
114 changes: 114 additions & 0 deletions src/main/kotlin/io/billie/invoices/data/InvoiceRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package io.billie.invoices.data

import com.google.gson.Gson
import io.billie.invoices.data.exceptions.NoOrderForInvoice
import io.billie.invoices.model.Product
import io.billie.invoices.model.FullInvoice
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.dao.DataIntegrityViolationException
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.ResultSetExtractor
import org.springframework.jdbc.core.RowMapper
import org.springframework.jdbc.support.GeneratedKeyHolder
import org.springframework.jdbc.support.KeyHolder
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.sql.ResultSet
import java.sql.Timestamp
import java.time.LocalDateTime
import java.util.*

@Repository
class InvoiceRepository {

private val gson = Gson()

@Autowired
lateinit var jdbcTemplate: JdbcTemplate

@Transactional
fun createInvoiceIfNeeded(orderId: UUID, timestamp: LocalDateTime): UUID {
val maybeInvoiceId = findInvoiceIfExists(orderId)
if (maybeInvoiceId != null)
return maybeInvoiceId
else {
try {
val keyHolder: KeyHolder = GeneratedKeyHolder()
jdbcTemplate.update(
{ connection ->
val ps = connection.prepareStatement(
"INSERT INTO organisations_schema.invoices(order_id, invoice_timestamp) VALUES(?, ?)",
arrayOf("id")
)
ps.setObject(1, orderId)
ps.setTimestamp(2, Timestamp.valueOf(timestamp))
ps
},
keyHolder
)
return keyHolder.getKeyAs(UUID::class.java)!!
} catch (e: DataIntegrityViolationException) {
throw NoOrderForInvoice("No order for invoice creation is found: ${e.message}")
}
}
}

@Transactional(readOnly=true)
fun findInvoices(): List<FullInvoice> = jdbcTemplate.query(fullInvoiceQuery(""), fullInvoiceMapper())

@Transactional(readOnly = true)
fun getFullInvoice(invoiceUUID: UUID): FullInvoice {
val specificIdWhereClause = "WHERE i.id = '$invoiceUUID'"
return jdbcTemplate.query(fullInvoiceQuery(specificIdWhereClause), fullInvoiceMapper()).first()
}


private fun findInvoiceIfExists(orderId: UUID): UUID? {
val maybeInvoiceId: UUID? = jdbcTemplate.query(
"select id from organisations_schema.invoices i WHERE i.order_id = ?",
ResultSetExtractor {
if (it.next()) it.getObject(1, UUID::class.java)
else null
},
orderId
)
return maybeInvoiceId
}

private fun fullInvoiceQuery(optionalIdFilter: String) =
"""
SELECT
i.id as id,
i.is_paid as is_paid,
i.invoice_timestamp as invoice_timestamp,
i.order_id as order_id,
org.name as organisation_name,
org.VAT_number as organisation_VAT_number,
o.customer_name as customer_name,
o.customer_VAT_number as customer_VAT_number,
o.customer_address as customer_address,
o.total_gross as total_gross,
o.products as products
FROM organisations_schema.invoices i
INNER JOIN organisations_schema.orders o on i.order_id::uuid = o.order_id::uuid
INNER JOIN organisations_schema.organisations org on o.organisation_id::uuid = org.id::uuid
$optionalIdFilter
""".trimIndent()

private fun fullInvoiceMapper() = RowMapper<FullInvoice> { it: ResultSet, _: Int ->
FullInvoice(
it.getObject("id", UUID::class.java),
it.getBoolean("is_paid"),
it.getTimestamp("invoice_timestamp").toLocalDateTime(),
it.getObject("order_id", UUID::class.java),
it.getString("organisation_name"),
it.getString("organisation_VAT_number"),
it.getString("customer_name"),
it.getString("customer_VAT_number"),
it.getString("customer_address"),
it.getDouble("total_gross"),
gson.fromJson(it.getString("products"), Array<Product>::class.java).toList()
)
}

}
86 changes: 86 additions & 0 deletions src/main/kotlin/io/billie/invoices/data/OrderRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package io.billie.invoices.data

import com.google.gson.Gson
import io.billie.invoices.data.exceptions.DuplicatedRecord
import io.billie.invoices.data.exceptions.NoOrganisationForOrder
import io.billie.invoices.model.Order
import io.billie.invoices.model.Product
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.dao.DataIntegrityViolationException
import org.springframework.dao.DuplicateKeyException
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.sql.ResultSet
import java.sql.Timestamp
import java.util.*

@Repository
class OrderRepository {

private val gson = Gson()

@Autowired
lateinit var jdbcTemplate: JdbcTemplate

@Transactional
fun create(order: Order) = createOrder(order)

@Transactional(readOnly=true)
fun findOrders(): List<Order> {
return jdbcTemplate.query(
"SELECT * FROM organisations_schema.orders",
orderMapper()
)
}

private fun createOrder(order: Order) {
try {
jdbcTemplate.update { connection ->
val ps = connection.prepareStatement("""
INSERT INTO organisations_schema.orders (
order_id,
order_timestamp,
organisation_id,
customer_name,
customer_VAT_number,
customer_address,
total_gross,
products
) VALUES (?, ?, ?, ?, ?, ?, ?, ?::jsonb)
""".trimIndent()
)
ps.setObject(1, order.orderId)
ps.setTimestamp(2, Timestamp.valueOf(order.orderTimestamp))
ps.setObject(3, order.organisationId)
ps.setString(4, order.customerName)
ps.setString(5, order.customerVATNumber)
ps.setString(6, order.customerAddress)
ps.setDouble(7, order.totalGross)
ps.setString(8, gson.toJson(order.products))
ps
}
} catch (e: DuplicateKeyException) {
throw DuplicatedRecord(order.orderId)
} catch (e: DataIntegrityViolationException) {
throw NoOrganisationForOrder("""
|Looks like there is no organisation for which order is trying to be created.
|Check if ${order.organisationId} exists on the platform and add it if needed.""".trimMargin())
}
}

private fun orderMapper() = RowMapper<Order> { it: ResultSet, _: Int ->
Order(
it.getObject("order_id", UUID::class.java),
it.getTimestamp("order_timestamp").toLocalDateTime(),
it.getObject("organisation_id", UUID::class.java),
it.getString("customer_name"),
it.getString("customer_VAT_number"),
it.getString("customer_address"),
it.getDouble("total_gross"),
gson.fromJson(it.getString("products"), Array<Product>::class.java).toList()
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.billie.invoices.data.exceptions

import java.util.UUID

class DuplicatedRecord(val id: UUID) : RuntimeException()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.billie.invoices.data.exceptions

class NoOrderForInvoice(val msg: String): RuntimeException()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.billie.invoices.data.exceptions

class NoOrganisationForOrder(val msg: String): RuntimeException()
9 changes: 9 additions & 0 deletions src/main/kotlin/io/billie/invoices/dto/CustomerOverview.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.billie.invoices.dto

import com.fasterxml.jackson.annotation.JsonProperty

data class CustomerOverview(
val name: String,
@JsonProperty("vat_number") val vatNumber: String,
val address: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.billie.invoices.dto

import com.fasterxml.jackson.annotation.JsonProperty

data class OrganisationOverview(
val name: String,
@JsonProperty("vat_number") val vatNumber: String,
//TODO add address: val address: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.billie.invoices.dto.request

import com.fasterxml.jackson.annotation.JsonProperty
import java.util.UUID

data class InvoiceRequest(
@JsonProperty("order_id") val orderId: UUID
)
14 changes: 14 additions & 0 deletions src/main/kotlin/io/billie/invoices/dto/request/OrderRequest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.billie.invoices.dto.request

import com.fasterxml.jackson.annotation.JsonProperty
import io.billie.invoices.dto.CustomerOverview
import io.billie.invoices.model.Product
import java.util.*

data class OrderRequest(
@JsonProperty("order_id") val orderId: UUID,
@JsonProperty("total_gross") val totalGross: Double,
@JsonProperty("organisation_id") val organisationId: UUID,
val customer: CustomerOverview,
val products: List<Product>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.billie.invoices.dto.response

import com.fasterxml.jackson.annotation.JsonProperty
import io.billie.invoices.dto.CustomerOverview
import io.billie.invoices.dto.OrganisationOverview
import io.billie.invoices.model.Product
import java.util.UUID

data class FullInvoiceResponse(
val id: UUID,
@JsonProperty("order_id") val orderId: UUID,
@JsonProperty("is_paid") val isPaid: Boolean,
@JsonProperty("issue_date") val formattedTime: String,
val organisation: OrganisationOverview,
val customer: CustomerOverview,
@JsonProperty("total_gross") val totalGross: Double,
val products: List<Product>
)
16 changes: 16 additions & 0 deletions src/main/kotlin/io/billie/invoices/dto/response/OrderResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.billie.invoices.dto.response

import com.fasterxml.jackson.annotation.JsonProperty
import io.billie.invoices.dto.CustomerOverview
import io.billie.invoices.model.Product
import java.time.LocalDateTime
import java.util.*

data class OrderResponse(
@JsonProperty("order_id") val orderId: UUID,
@JsonProperty("order_timestamp") val orderTimestamp: LocalDateTime,
@JsonProperty("total_gross") val totalGross: Double,
@JsonProperty("organisation_id") val organisationId: UUID,
val customer: CustomerOverview,
val products: List<Product>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.billie.invoices.dto.response

data class SimpleSuccessResponse(val comment: String)
18 changes: 18 additions & 0 deletions src/main/kotlin/io/billie/invoices/mapper/InvoiceMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.billie.invoices.mapper

import io.billie.invoices.dto.CustomerOverview
import io.billie.invoices.dto.response.FullInvoiceResponse
import io.billie.invoices.dto.OrganisationOverview
import io.billie.invoices.model.FullInvoice
import java.time.format.DateTimeFormatter

fun FullInvoice.toResponse(): FullInvoiceResponse = FullInvoiceResponse(
id = this.id,
orderId = this.orderId,
isPaid = this.isPaid,
formattedTime = this.invoiceTimestamp.format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss")),
organisation = OrganisationOverview(this.organisationName, this.organisationVATNumber),
customer = CustomerOverview(this.customerName, this.customerVATNumber, this.customerAddress),
totalGross = this.totalGross,
products = this.products
)
27 changes: 27 additions & 0 deletions src/main/kotlin/io/billie/invoices/mapper/OrderMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.billie.invoices.mapper

import io.billie.invoices.dto.CustomerOverview
import io.billie.invoices.dto.request.OrderRequest
import io.billie.invoices.dto.response.OrderResponse
import io.billie.invoices.model.Order
import java.time.LocalDateTime

fun OrderRequest.toModel(timestamp: LocalDateTime): Order = Order(
orderId = this.orderId,
orderTimestamp = timestamp,
totalGross = this.totalGross,
organisationId = this.organisationId,
customerName = this.customer.name,
customerAddress = this.customer.address,
customerVATNumber = this.customer.vatNumber,
products = this.products
)

fun Order.toResponse(): OrderResponse = OrderResponse(
orderId = this.orderId,
orderTimestamp = this.orderTimestamp,
totalGross = this.totalGross,
organisationId = this.organisationId,
customer = CustomerOverview(this.customerName, this.customerVATNumber, this.customerAddress),
products = this.products
)
Loading