feat(TODO): update phases for orders, billing, and calendar features
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 43s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 43s
- Marked orders and billing tasks as complete in the TODO list, detailing the associated components and APIs. - Introduced a new phase for calendar features, outlining tasks for navigation, data loading, and event management. - Enhanced the AppDependencies to include BillingApi and MemberOrdersApi for improved billing and order management. - Updated AppRoot and SettingsScreen to incorporate billing and orders sections, enhancing user navigation and functionality.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package de.tt_tagebuch.shared.api
|
||||
|
||||
import de.tt_tagebuch.shared.api.http.AuthedHttpClient
|
||||
import de.tt_tagebuch.shared.api.models.BillingCreateRunBody
|
||||
import de.tt_tagebuch.shared.api.models.BillingCreateRunEnvelope
|
||||
import de.tt_tagebuch.shared.api.models.BillingGenerateBody
|
||||
import de.tt_tagebuch.shared.api.models.BillingGenerateEnvelope
|
||||
import de.tt_tagebuch.shared.api.models.BillingHoursPreviewEnvelope
|
||||
import de.tt_tagebuch.shared.api.models.BillingRunsEnvelope
|
||||
import de.tt_tagebuch.shared.api.models.BillingSettingsEnvelope
|
||||
import de.tt_tagebuch.shared.api.models.BillingTemplatesEnvelope
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.forms.MultiPartFormDataContent
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.readBytes
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.contentType
|
||||
|
||||
class BillingApi(
|
||||
private val client: AuthedHttpClient,
|
||||
) {
|
||||
suspend fun listTemplates(clubId: Int): BillingTemplatesEnvelope =
|
||||
client.http.get("/api/billing/templates/$clubId").body()
|
||||
|
||||
suspend fun uploadTemplate(clubId: Int, name: String, description: String, pdfBytes: ByteArray, filename: String = "template.pdf") {
|
||||
client.http.post("/api/billing/templates/$clubId") {
|
||||
contentType(ContentType.MultiPart.FormData)
|
||||
setBody(
|
||||
MultiPartFormDataContent(
|
||||
formData {
|
||||
append("name", name)
|
||||
append("description", description)
|
||||
append(
|
||||
"templatePdf",
|
||||
pdfBytes,
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, "application/pdf")
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteTemplate(templateId: Int) {
|
||||
client.http.delete("/api/billing/templates/$templateId")
|
||||
}
|
||||
|
||||
suspend fun listRuns(clubId: Int): BillingRunsEnvelope =
|
||||
client.http.get("/api/billing/runs/$clubId").body()
|
||||
|
||||
suspend fun getSettings(clubId: Int): BillingSettingsEnvelope =
|
||||
client.http.get("/api/billing/settings/$clubId").body()
|
||||
|
||||
suspend fun hoursPreview(clubId: Int, monthFrom: String, monthTo: String): BillingHoursPreviewEnvelope =
|
||||
client.http.get("/api/billing/hours-preview/$clubId") {
|
||||
parameter("monthFrom", monthFrom)
|
||||
parameter("monthTo", monthTo)
|
||||
}.body()
|
||||
|
||||
suspend fun createRun(clubId: Int, body: BillingCreateRunBody): BillingCreateRunEnvelope =
|
||||
client.http.post("/api/billing/runs/$clubId") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(body)
|
||||
}.body()
|
||||
|
||||
suspend fun generateRun(runId: Int, locale: String = "de-DE"): BillingGenerateEnvelope =
|
||||
client.http.post("/api/billing/runs/$runId/generate") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(BillingGenerateBody(locale = locale))
|
||||
}.body()
|
||||
|
||||
suspend fun downloadRunPdf(runId: Int): ByteArray =
|
||||
client.http.get("/api/billing/runs/$runId/download").readBytes()
|
||||
|
||||
suspend fun deleteRun(runId: Int) {
|
||||
client.http.delete("/api/billing/runs/$runId")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package de.tt_tagebuch.shared.api
|
||||
|
||||
import de.tt_tagebuch.shared.api.http.AuthedHttpClient
|
||||
import de.tt_tagebuch.shared.api.models.MemberOrderCreateBody
|
||||
import de.tt_tagebuch.shared.api.models.MemberOrderEnvelope
|
||||
import de.tt_tagebuch.shared.api.models.MemberOrderPatchBody
|
||||
import de.tt_tagebuch.shared.api.models.MemberOrdersListEnvelope
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.patch
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.contentType
|
||||
|
||||
class MemberOrdersApi(
|
||||
private val client: AuthedHttpClient,
|
||||
) {
|
||||
suspend fun listGlobal(): MemberOrdersListEnvelope =
|
||||
client.http.get("/api/member-orders/global").body()
|
||||
|
||||
suspend fun listForMember(clubId: Int, memberId: Int): MemberOrdersListEnvelope =
|
||||
client.http.get("/api/member-orders/$clubId/$memberId").body()
|
||||
|
||||
suspend fun create(clubId: Int, memberId: Int, body: MemberOrderCreateBody): MemberOrderEnvelope =
|
||||
client.http.post("/api/member-orders/$clubId/$memberId") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(body)
|
||||
}.body()
|
||||
|
||||
suspend fun update(clubId: Int, memberId: Int, orderId: Int, body: MemberOrderPatchBody): MemberOrderEnvelope =
|
||||
client.http.patch("/api/member-orders/$clubId/$memberId/$orderId") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(body)
|
||||
}.body()
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package de.tt_tagebuch.shared.api.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class BillingTemplateDto(
|
||||
val id: Int,
|
||||
val clubId: Int? = null,
|
||||
val name: String = "",
|
||||
val description: String? = null,
|
||||
val version: Int = 1,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingTemplatesEnvelope(
|
||||
val success: Boolean = false,
|
||||
val templates: List<BillingTemplateDto> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingRunDto(
|
||||
val id: Int,
|
||||
val clubId: Int? = null,
|
||||
val templateId: Int? = null,
|
||||
val name: String? = null,
|
||||
val periodStart: String? = null,
|
||||
val periodEnd: String? = null,
|
||||
val selfRecipientName: String? = null,
|
||||
val hourlyRate: Double = 0.0,
|
||||
val computedHoursTotal: Double? = null,
|
||||
val status: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingRunsEnvelope(
|
||||
val success: Boolean = false,
|
||||
val runs: List<BillingRunDto> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingUserSettingsDto(
|
||||
val lastHourlyRate: Double? = null,
|
||||
val lastSelfRecipientName: String? = null,
|
||||
val lastLocationText: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingSettingsEnvelope(
|
||||
val success: Boolean = false,
|
||||
val settings: BillingUserSettingsDto? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingSessionPreviewDto(
|
||||
val date: String? = null,
|
||||
val startTime: String? = null,
|
||||
val endTime: String? = null,
|
||||
val durationHours: Double? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingHoursPreviewEnvelope(
|
||||
val success: Boolean = false,
|
||||
val computedHoursTotal: Double? = null,
|
||||
val sessions: List<BillingSessionPreviewDto> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingCreateRunBody(
|
||||
val templateId: Int,
|
||||
val monthFrom: String,
|
||||
val monthTo: String,
|
||||
val selfRecipientName: String? = null,
|
||||
val iban: String? = null,
|
||||
val ibanWithoutCountry: Boolean = false,
|
||||
val hourlyRate: Double,
|
||||
val sessionLabel: String? = null,
|
||||
val sameAccountCheckbox: Boolean = false,
|
||||
val omitSelfRecipientName: Boolean = false,
|
||||
val omitIban: Boolean = false,
|
||||
val omitLocationText: Boolean = false,
|
||||
val omitDocumentDate: Boolean = false,
|
||||
val omitSessionLabel: Boolean = false,
|
||||
val locationText: String? = null,
|
||||
val documentDate: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingCreateRunEnvelope(
|
||||
val success: Boolean = false,
|
||||
val run: BillingRunDto? = null,
|
||||
val error: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingGenerateBody(
|
||||
val locale: String = "de-DE",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BillingGenerateEnvelope(
|
||||
val success: Boolean = false,
|
||||
val error: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,80 @@
|
||||
package de.tt_tagebuch.shared.api.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OrderMemberSnippetDto(
|
||||
val id: Int? = null,
|
||||
val firstName: String? = null,
|
||||
val lastName: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class OrderClubSnippetDto(
|
||||
val id: Int? = null,
|
||||
val name: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MemberOrderHistoryEntryDto(
|
||||
val id: Int? = null,
|
||||
val status: String? = null,
|
||||
val changedAt: String? = null,
|
||||
val cost: Double? = null,
|
||||
val paidAmount: Double? = null,
|
||||
val budget: Double? = null,
|
||||
val paidConfirmed: Boolean? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MemberOrderDto(
|
||||
val id: Int,
|
||||
val memberId: Int? = null,
|
||||
val clubId: Int? = null,
|
||||
val item: String = "",
|
||||
val status: String = "requested",
|
||||
val orderDate: String? = null,
|
||||
val statusDate: String? = null,
|
||||
val createdAt: String? = null,
|
||||
val updatedAt: String? = null,
|
||||
val cost: Double = 0.0,
|
||||
val paidAmount: Double = 0.0,
|
||||
val budget: Double = 0.0,
|
||||
val paidConfirmed: Boolean = false,
|
||||
val openAmount: Double = 0.0,
|
||||
val member: OrderMemberSnippetDto? = null,
|
||||
val club: OrderClubSnippetDto? = null,
|
||||
val historyEntries: List<MemberOrderHistoryEntryDto> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MemberOrdersListEnvelope(
|
||||
val success: Boolean = false,
|
||||
val orders: List<MemberOrderDto> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MemberOrderEnvelope(
|
||||
val success: Boolean = false,
|
||||
val order: MemberOrderDto? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MemberOrderCreateBody(
|
||||
val item: String,
|
||||
val status: String = "requested",
|
||||
val cost: Double = 0.0,
|
||||
val paidAmount: Double = 0.0,
|
||||
val budget: Double = 0.0,
|
||||
val paidConfirmed: Boolean = false,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MemberOrderPatchBody(
|
||||
val item: String,
|
||||
val status: String,
|
||||
val cost: Double,
|
||||
val paidAmount: Double,
|
||||
val budget: Double,
|
||||
val paidConfirmed: Boolean,
|
||||
)
|
||||
Reference in New Issue
Block a user