Buchungen funktioniern mitsamt modal und laufendem saldo
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
##
|
||||
## Datum: 2024-10-17_11-38
|
||||
## Name: Christian Oswald
|
||||
## Projekt: etatverwaltung
|
||||
## Datei: f_airdatepicker.R
|
||||
## Kommentar: Funktionen für den Airdatepicker
|
||||
##
|
||||
|
||||
## * Update unter Berücksichtigung nicht vorhandern Werte
|
||||
f_updateAirdate <- function(session, id, wert){
|
||||
if(is.na(wert)){
|
||||
updateAirDateInput(session, id, clear = TRUE)
|
||||
}else{
|
||||
updateAirDateInput(session, id, value = wert)
|
||||
}
|
||||
}
|
||||
|
||||
## * Format für den Picker
|
||||
f_airdatepicker_UI <- function(id, label, value) {
|
||||
ns <- NS(id)
|
||||
airDatepickerInput(
|
||||
id,
|
||||
label = label,
|
||||
value = value,
|
||||
width = "100%",
|
||||
update_on = "close",
|
||||
readonly = FALSE, # Manuelle Eingabe erlauben
|
||||
onkeydown = "Shiny.setInputValue(this.id, this.value, {priority: 'event'});", # Übergabe an Shiny
|
||||
|
||||
|
||||
todayButton = T)
|
||||
}
|
||||
|
||||
# f_airdatepicker_UI(id, label, value)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
f_reactable <- function(daten, coldefs = NULL){
|
||||
reactable(daten,
|
||||
selection = "single",
|
||||
bordered = TRUE,
|
||||
defaultPageSize = 17,
|
||||
filterable = TRUE,
|
||||
highlight = TRUE,
|
||||
striped = F,
|
||||
theme = reactableTheme(
|
||||
highlightColor = "#00f200",
|
||||
borderColor = "#dfe2e5",
|
||||
rowSelectedStyle = list(backgroundColor = "#98F5FF"),
|
||||
),
|
||||
compact = TRUE,
|
||||
rowStyle = list(cursor = "pointer"),
|
||||
onClick = "select",
|
||||
columns = coldefs
|
||||
)
|
||||
}
|
||||
@@ -6,8 +6,8 @@ read_accounts <- function(conn){
|
||||
|
||||
get_account_choices <- function(conn) {
|
||||
tbl(conn, "accounts") |>
|
||||
select(id, konto) |>
|
||||
select(id, account_name) |>
|
||||
collect() |>
|
||||
arrange(konto) |>
|
||||
(\(df) setNames(df$id, df$konto))()
|
||||
arrange(account_name) |>
|
||||
(\(df) setNames(df$id, df$account_name))()
|
||||
}
|
||||
|
||||
+24
-68
@@ -1,92 +1,48 @@
|
||||
buchungenUI <- function(id) {
|
||||
ns <- NS(id)
|
||||
tagList(
|
||||
reactableOutput(ns("buchungen_table"))
|
||||
reactableOutput(ns("buchungen_table")),
|
||||
reactableOutput(ns("details_table")),
|
||||
postingModuleUI(ns("posting_modal"))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
buchungenServer <- function(id) {
|
||||
moduleServer( id, function(input, output, session) {
|
||||
moduleServer(id, function(input, output, session) {
|
||||
ns <- session$ns
|
||||
|
||||
postings <- reactiveVal(read_buch_tabelle(conn))
|
||||
|
||||
# NEU: Eine reactive variable für die ID, die wir editieren wollen
|
||||
# current_entry_id <- reactiveVal(NULL)
|
||||
|
||||
# NEU: Den Modul-Server EINMALIG hier oben starten (nicht im Observer!)
|
||||
# entryEditServer("entry_edit", entry_id = current_entry_id, conn = conn)
|
||||
|
||||
output$buchungen_table <- renderReactable({
|
||||
reactable(
|
||||
postings(),
|
||||
onClick = "expand",
|
||||
selection = "single",
|
||||
details = function(index) {
|
||||
entry_id <- postings()$entry_id[index]
|
||||
detail_rows <- postings()[postings()$entry_id == entry_id, ]
|
||||
|
||||
div(
|
||||
style = "padding: 10px; background: #f4f4f4; border-left: 4px solid #3c8dbc; max-width: 800px; margin-left: auto",
|
||||
|
||||
reactable(
|
||||
dplyr::select(detail_rows, konto, projektname, amount),
|
||||
fullWidth = TRUE,
|
||||
columns = list(
|
||||
konto = colDef(name = "Konto", minWidth = 150),
|
||||
projektname = colDef(name = "Projekt", minWidth = 150),
|
||||
amount = colDef(name = "Betrag", minWidth = 80)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
columns = list(
|
||||
id = colDef(name = "ID", minWidth = 80),
|
||||
valuta = colDef(name = "Wertstellung", minWidth = 80),
|
||||
konto = colDef(name = "Kontoname", minWidth = 200),
|
||||
entry_id = colDef(show = FALSE)
|
||||
)
|
||||
)
|
||||
# req(postings())
|
||||
f_reactable(postings(), coldefs = coldef_entries_tabelle)
|
||||
})
|
||||
|
||||
# Modal zum editieren
|
||||
selected <- reactive(getReactableState("buchungen_table", "selected"))
|
||||
observeEvent(selected(),{
|
||||
idwert <- postings()[selected(), "id"]
|
||||
selected_row <- read_posting(conn, idwert) %>%
|
||||
mutate(verzicht = ifelse(is.na(verzicht), F, verzicht))
|
||||
showModal(modalDialog(
|
||||
title = "Buchung bearbeiten",
|
||||
size = "l",
|
||||
tags$style(HTML(".modal-dialog { max-width: 90% !important; width: 90% !important; }")),
|
||||
|
||||
entryEditUI(ns("entry_edit")),
|
||||
footer = tagList(
|
||||
modalButton("Abbrechen"),
|
||||
actionButton(ns("speichern"), "Speichern")
|
||||
)
|
||||
))
|
||||
|
||||
entryEditServer("entry_edit", entry_id = selected_row$entry_id, conn = conn)
|
||||
|
||||
}, ignoreInit = TRUE)
|
||||
|
||||
observeEvent(input$speichern, {
|
||||
browser()
|
||||
dbExecute(conn, "UPDATE postings SET
|
||||
account_id = ?, project_id = ?, amount = ?, valuta = ?,
|
||||
notiz = ?, rechnungsnummer = ?, betrag_muenzen = ?, verzicht = ?
|
||||
WHERE id = ?",
|
||||
params = list(
|
||||
input$account_id, input$project_id, input$amount, input$valuta,
|
||||
input$notiz, input$rechnungsnummer, input$betrag_muenzen, input$verzicht,
|
||||
selected_row$id
|
||||
)
|
||||
observeEvent(selected(), {
|
||||
idwert <- postings()[selected(),"entry_id"] %>% pull
|
||||
details <- read_buch_tabelle(conn, trans_id = idwert)
|
||||
output$details_table <- renderReactable(
|
||||
f_reactable(details, coldefs = coldef_entries_tabelle)
|
||||
)
|
||||
|
||||
removeModal()
|
||||
buchungen(read_buch_tabelle(conn)) # Tabelle neu laden
|
||||
})
|
||||
|
||||
selected_det <- reactive(getReactableState("details_table", "selected"))
|
||||
observeEvent(selected_det(),{
|
||||
idwert <- postings()[selected(),"id"] %>% pull
|
||||
postingModuleServer("posting_modal",conn, idwert)
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
+35
-77
@@ -1,93 +1,51 @@
|
||||
# entry_edit_mod.R
|
||||
entryEditUI <- function(id) {
|
||||
|
||||
# --- MODUL UI ---
|
||||
postingModuleUI <- function(id) {
|
||||
ns <- NS(id)
|
||||
tagList(
|
||||
|
||||
uiOutput(ns("entry_ui")),
|
||||
uiOutput(ns("postings_ui"))
|
||||
)
|
||||
}
|
||||
|
||||
entryEditServer <- function(id, entry_id, conn) {
|
||||
# --- MODUL SERVER ---
|
||||
postingModuleServer <- function(id, conn, idwert) {
|
||||
moduleServer(id, function(input, output, session) {
|
||||
ns <- session$ns
|
||||
ns <- session$ns # Wichtig für das Namespacing innerhalb des Modals
|
||||
record <- read_posting(conn, idwert)
|
||||
trans <- dbxSelect(conn, paste0("SELECT * FROM entries WHERE id=", record$entry_id))
|
||||
# Modal öffnen
|
||||
|
||||
entry_postings <- reactiveVal(read_postings_by_entry(conn, entry_id))
|
||||
entry_data <- reactiveVal(read_entry(conn, entry_id))
|
||||
showModal(modalDialog(
|
||||
title = "Buchung-Eingabe",
|
||||
|
||||
output$entry_ui <- renderUI({
|
||||
e <- entry_data()
|
||||
tagList(
|
||||
fluidRow(
|
||||
column(6, selectizeInput(ns("contact_id"), "Kontakt",
|
||||
choices = get_contact_choices(conn),
|
||||
selected = e$contact_id)),
|
||||
column(6, textInput(ns("purpose"), "Verwendungszweck", value = e$purpose))
|
||||
|
||||
f_airdatepicker_UI(ns("valuta"), "Wertstellung", record$valuta),
|
||||
f_airdatepicker_UI(ns("buchungsdatum"), "Buchungsdatum", record$booking_date),
|
||||
selectizeInput(ns("kontakt"), "Kontakt:",selected = trans$adress_id, choices = get_contact_choices(conn), width = "100%"),
|
||||
selectizeInput(ns("konto"), "Kontoname:",selected = record$account_id, choices = get_account_choices(conn), width = "100%"),
|
||||
selectizeInput(ns("projekt"), "Projektname", selected = NULL, choices = get_project_choices(conn), width = "100%"),
|
||||
splitLayout(cellWidths = c("70%", "30%"),
|
||||
numericInput(ns("amount"), "Betrag:", value = record$amount, width = "100%"),
|
||||
numericInput(ns("coin_share_amount"), "Münzne:", value = record$coin_share_amount, width = "100%")
|
||||
),
|
||||
hr()
|
||||
)
|
||||
})
|
||||
|
||||
# Choices für contact nach dem Flush setzen
|
||||
|
||||
|
||||
output$postings_ui <- renderUI({
|
||||
alle <- entry_postings()
|
||||
lapply(seq_len(nrow(alle)), function(i) {
|
||||
print(paste("project selected:", alle$project_id[i]))
|
||||
print(head(get_project_choices(conn)))
|
||||
print(class(get_project_choices(conn)))
|
||||
div(
|
||||
style = "border-left: 3px solid #3c8dbc; padding: 5px; margin-bottom: 5px",
|
||||
fluidRow(
|
||||
column(3,
|
||||
selectInput(ns(paste0("account_id_", i)), "Konto",
|
||||
choices = get_account_choices(conn),
|
||||
selected = as.character(alle$account_id[i]))
|
||||
textAreaInput(ns("trans_notiz"), label= "Notiz (Transaktion)", value = trans$note, width = "100%"),
|
||||
splitLayout(cellWidths = c("50%", "50%"),
|
||||
numericInput(ns("receipt_id"), "Umsatz_id:", value = record$receipt_id, width = "100%"),
|
||||
numericInput(ns("wiso_id"), "Wiso_id:", value = record$wiso_id, width = "100%")
|
||||
),
|
||||
column(3, selectizeInput(ns(paste0("project_id_", i)), "Projekt",
|
||||
choices = get_project_choices(conn),
|
||||
selected = ifelse(is.na(alle$project_id[i]), "", as.character(alle$project_id[i])))
|
||||
#
|
||||
footer = tagList(
|
||||
modalButton("Abbrechen"),
|
||||
actionButton(ns("confirm"), "Bestätigen", class = "btn-success")
|
||||
),
|
||||
column(2, numericInput(ns(paste0("amount_", i)), "Betrag", value = alle$amount[i])),
|
||||
column(3, textInput(ns(paste0("notiz_", i)), "Notiz", value = alle$notiz[i])),
|
||||
column(1, actionButton(ns(paste0("delete_", i)), "", icon = icon("trash"), class = "btn-danger btn-sm"))
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
easyClose = TRUE
|
||||
))
|
||||
|
||||
# Choices befüllen nachdem renderUI fertig ist
|
||||
observe({
|
||||
alle <- entry_postings()
|
||||
e <- entry_data()
|
||||
req(nrow(alle) > 0, nrow(e) > 0)
|
||||
|
||||
session$onFlushed(function() {
|
||||
# Contact
|
||||
updateSelectizeInput(session, "contact_id",
|
||||
choices = get_contact_choices(conn),
|
||||
selected = e$contact_id)
|
||||
|
||||
# Postings
|
||||
lapply(seq_len(nrow(alle)), function(i) {
|
||||
updateSelectizeInput(session, paste0("account_id_", i),
|
||||
choices = get_account_choices(conn),
|
||||
selected = alle$account_id[i])
|
||||
updateSelectizeInput(session, paste0("project_id_", i),
|
||||
choices = get_project_choices(conn),
|
||||
selected = alle$project_id[i])
|
||||
# Aktion beim Bestätigen
|
||||
observeEvent(input$confirm, ignoreInit = T, {
|
||||
message("Eingabe im Modul ", id, ": ", input$user_name)
|
||||
removeModal()
|
||||
})
|
||||
}, once = TRUE)
|
||||
})
|
||||
|
||||
# Speichern-Logik
|
||||
observeEvent(input$speichern, {
|
||||
alle <- entry_postings()
|
||||
lapply(seq_len(nrow(alle)), function(i) {
|
||||
# update posting i in DB
|
||||
})
|
||||
})
|
||||
|
||||
#
|
||||
})
|
||||
}
|
||||
+53
-7
@@ -1,5 +1,10 @@
|
||||
read_buch_tabelle <- function(conn){
|
||||
read_buch_tabelle <- function(conn, trans_id = NULL){
|
||||
if(!is.null(trans_id)){
|
||||
postings <- tbl(conn, "postings") %>%
|
||||
filter(entry_id == trans_id)
|
||||
} else {
|
||||
postings <- tbl(conn, "postings")
|
||||
}
|
||||
entries <- tbl(conn, "entries")
|
||||
accounts <- tbl(conn, "accounts")
|
||||
projects <- tbl(conn, "projects")
|
||||
@@ -9,14 +14,55 @@ read_buch_tabelle <- function(conn){
|
||||
left_join(contacts, by = c("contact_id" = "id")) |> # contact_id jetzt verfügbar
|
||||
left_join(accounts, by = c("account_id" = "id")) |>
|
||||
left_join(projects, by = c("project_id" = "id")) |>
|
||||
select(id, valuta, kontoname, projektname, display_name, amount, entry_id) |>
|
||||
collect()
|
||||
select(id, valuta, account_name, projektname, display_name, amount, entry_id) |>
|
||||
collect() %>%
|
||||
mutate(saldo = 0)
|
||||
}
|
||||
|
||||
read_posting <- function(conn, id){
|
||||
dbxSelect(conn, paste0("SELECT * FROM postings WHERE id =", id))
|
||||
}
|
||||
read_postings_by_entry <- function(conn, id){
|
||||
dbxSelect(conn, paste0("SELECT * FROM postings WHERE entry_id =", id))
|
||||
}
|
||||
tbl(conn, "accounts") %>% collect %>% str
|
||||
|
||||
|
||||
coldef_entries_tabelle <-
|
||||
list(
|
||||
id = colDef(name = "ID", width = 80),
|
||||
valuta = colDef(name = "Wertstellung", width = 120),
|
||||
account_name = colDef(name = "Kontoname", width = 200),
|
||||
projektname = colDef(name = "Projektname", width = 150),
|
||||
display_name = colDef(name = "Kontakt", width = 250),
|
||||
amount = colDef(name = "Betrag", width = 120,
|
||||
format = colFormat(prefix = "", separators = TRUE, digits = 2),
|
||||
style = function(value) {
|
||||
color <- if (value < 0) "#e06666" else "#2b2b2b" # Rot bei negativ, sonst dunkelgrau
|
||||
list(color = color, fontWeight = "bold") # Gibt CSS-Styles zurück
|
||||
}
|
||||
),
|
||||
entry_id = colDef(name ="trans_id", width = 120),
|
||||
saldo = colDef(
|
||||
name = "Saldo",
|
||||
width = 140,
|
||||
html = TRUE,
|
||||
cell = JS("
|
||||
function(cellInfo, state) {
|
||||
const page = state.page || 0
|
||||
const pageSize = state.pageSize || state.sortedData.length
|
||||
const globalIndex = page * pageSize + cellInfo.viewIndex
|
||||
|
||||
let total = 0
|
||||
for (let i = 0; i <= globalIndex && i < state.sortedData.length; i++) {
|
||||
total += Number(state.sortedData[i].amount || 0)
|
||||
}
|
||||
|
||||
const color = total < 0 ? '#e06666' : '#2b2b2b'
|
||||
|
||||
return `<span style='color:${color}; font-weight:bold'>
|
||||
${total.toLocaleString('de-DE', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})}
|
||||
</span>`
|
||||
}
|
||||
")
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
library("dbplyr")
|
||||
library("tidyverse")
|
||||
library("shiny")
|
||||
library("shinyWidgets")
|
||||
library("DBI")
|
||||
library("RSQLite")
|
||||
library("dbx")
|
||||
@@ -15,5 +16,8 @@ conflicts_prefer(dplyr::filter)
|
||||
options(shiny.reactlog = TRUE)
|
||||
options(shiny.error = browser)
|
||||
|
||||
conn <- dbConnect(RSQLite::SQLite(), "db/development.sqlite3")
|
||||
conn <- dbConnect(RSQLite::SQLite(), "db/development.sqlite")
|
||||
sourceDirectory("R/")
|
||||
|
||||
dbReadTable(conn, "projects") %>% str
|
||||
dbGetQuery(conn, "PRAGMA table_info(postings);")
|
||||
|
||||
Reference in New Issue
Block a user