From 81302a85325dc7787fdced99fc83b68638e10608 Mon Sep 17 00:00:00 2001 From: Christian Oswald Date: Tue, 17 Mar 2026 17:33:47 +0100 Subject: [PATCH] verschiedene fehler behoben --- R/_funktionen/f_max_id.R | 2 +- R/_funktionen/f_table.R | 28 +++--- R/buchungen_mod.R | 89 +++++++++++-------- R/entry_edit_modal.R | 184 +++++++++++++++++++++------------------ db/development.sqlite | Bin 1011712 -> 1011712 bytes server.R | 2 +- 6 files changed, 168 insertions(+), 137 deletions(-) diff --git a/R/_funktionen/f_max_id.R b/R/_funktionen/f_max_id.R index d1390da..d416428 100644 --- a/R/_funktionen/f_max_id.R +++ b/R/_funktionen/f_max_id.R @@ -8,7 +8,7 @@ ## * Initialisierung ---- -max_id <- function(conn, tablle){ +max_id <- function(conn, tabelle){ paste0("SELECT max(id) FROM ", tabelle) %>% dbxSelect(conn,.) %>% pull diff --git a/R/_funktionen/f_table.R b/R/_funktionen/f_table.R index 2419b7e..13bfad1 100644 --- a/R/_funktionen/f_table.R +++ b/R/_funktionen/f_table.R @@ -1,20 +1,24 @@ - -f_reactable <- function(daten, coldefs = NULL){ - reactable(daten, - selection = "single", - bordered = TRUE, +f_reactable <- function(daten, coldefs = NULL, selection = "single", defaultSelected = NULL) { + reactable( + daten, + selection = selection, + defaultSelected = defaultSelected, # Ermöglicht das Wiederherstellen der Auswahl + pagination = TRUE, defaultPageSize = 17, + showPageSizeOptions = TRUE, filterable = TRUE, highlight = TRUE, - striped = F, - theme = reactableTheme( - highlightColor = "#00f200", - borderColor = "#dfe2e5", - rowSelectedStyle = list(backgroundColor = "#98F5FF"), - ), + bordered = TRUE, + striped = FALSE, compact = TRUE, + # Styling + theme = reactableTheme( + highlightColor = "#e6f7ff", # Etwas dezenter als knallgrün, optional + # borderColor = "#dfe2e5", + rowSelectedStyle = list(backgroundColor = "#98F5FF") + ), rowStyle = list(cursor = "pointer"), onClick = "select", columns = coldefs ) -} +} \ No newline at end of file diff --git a/R/buchungen_mod.R b/R/buchungen_mod.R index 244e1bf..4f98b22 100644 --- a/R/buchungen_mod.R +++ b/R/buchungen_mod.R @@ -1,59 +1,76 @@ +# module_buchungen.R + buchungenUI <- function(id) { ns <- NS(id) tagList( + h3("Hauptbuchungen"), reactableOutput(ns("buchungen_table")), + hr(), + h3("Details / Gegenbuchungen"), reactableOutput(ns("details_table")), - postingModuleUI(ns("posting_modal")), - actionBttn(ns("add_trans"), "Transaktion hinzu", size = "sm", style = "material-flat", color = "warning"), - actionBttn(ns("add_trans"), "Transaktion Löschen", size = "sm", style = "material-flat", color = "danger"), - actionBttn(ns("add_detail"), "Detail hinzu", size = "sm", style = "material-flat", color = "success") + br(), + actionBttn(ns("add_detail"), "Detail hinzufügen", size = "sm", style = "material-flat", color = "success"), + # Das UI-Element des Moduls (auch wenn es leer ist) + postingModuleUI(ns("posting_modal")) ) } - -buchungenServer <- function(id) { +buchungenServer <- function(id, conn) { moduleServer(id, function(input, output, session) { ns <- session$ns - postings <- reactiveVal(read_buch_tabelle(conn)) - details <- reactiveVal() - tabelle_neu <- reactiveVal() - # NEU: Eine reactive variable für die ID, die wir editieren wollen - # current_entry_id <- reactiveVal(NULL) + # Reactive States + postings_data <- reactiveVal(read_buch_tabelle(conn)) + details_data <- reactiveVal(NULL) + selected_trans_id <- reactiveVal(NULL) - # NEU: Den Modul-Server EINMALIG hier oben starten (nicht im Observer!) - # entryEditServer("entry_edit", entry_id = current_entry_id, conn = conn) + # Trigger-Objekt für das Modal: enthält post_id und einen Counter + # Der Counter erzwingt eine Reaktion, auch wenn die gleiche ID zweimal geklickt wird + modal_trigger <- reactiveVal(list(post_id = NULL, counter = 0)) + # --- MODUL-SERVER STARTEN (EINMALIG) --- + update_db_trigger <- postingModuleServer("posting_modal", conn, selected_trans_id, modal_trigger) + + # Haupttabelle rendern output$buchungen_table <- renderReactable({ - # req(postings()) - f_reactable(postings(), coldefs = coldef_entries_tabelle) + req(postings_data()) + f_reactable(daten = postings_data(), coldefs = coldef_entries_tabelle, selection = "single") }) - # Buchungen und gegenbuchungen anzeigen - selected <- reactive(getReactableState("buchungen_table", "selected")) - 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) - ) + # Details laden wenn Zeile gewählt wird + sel_details <- reactive(getReactableState("buchungen_table", "selected")) + observeEvent(sel_details(), ignoreInit = T, { + t_id <- postings_data()[sel_details(), "entry_id"] %>% pull() + selected_trans_id(t_id) + details_data(read_buch_tabelle(conn, trans_id = t_id)) }) - # Buchunge auswähle - selected_det <- reactive(getReactableState("details_table", "selected")) - observeEvent(selected_det(),{ - idwert <- postings()[selected(),"id"] %>% pull - tabelle_neu(postingModuleServer("posting_modal",conn, idwert)) + output$details_table <- renderReactable({ + req(details_data()) + f_reactable(details_data(), coldefs = coldef_entries_tabelle, selection = "single") }) - # Buchungstail hinzufügen - observeEvent(input$add_detail, ignoreInit = T, { - trans_id <- idwert <- postings()[selected(),"entry_id"] %>% pull - post_id <- max_id(conn, "posting") + 1 - tabelle_neu(postingModuleServer("posting_modal",conn, idwert, post_id + 1)) + + # Event: Detail hinzufügen (Neu) + observeEvent(input$add_detail, { + req(selected_trans_id()) + modal_trigger(list(post_id = NULL, counter = modal_trigger()$counter + 1)) }) - # Tabelle aktualisieren - observeEvent(tabelle_neu(), ignoreInit = T, { - details(read_buch_tabelle(conn, trans_id = idwert)) + + # Event: Detail editieren (Klick auf Detail-Tabelle) + sel_detail <- reactive(getReactableState("details_table", "selected")) + observeEvent(sel_detail(), ignoreInit = T, { + p_id <- details_data()$id[sel] + modal_trigger(list(post_id = p_id, counter = modal_trigger()$counter + 1)) + }) + + # --- DAS REAKTIVE UPDATE --- + # Wenn das Modal sagt "Fertig", laden wir die Details neu + observeEvent(update_db_trigger(), ignoreInit = TRUE, { + req(selected_trans_id()) + # Tabellen-Daten neu aus DB ziehen + details_data(read_buch_tabelle(conn, trans_id = selected_trans_id())) + # Optional: Auch Haupttabelle erneuern, falls sich Summen geändert haben + postings_data(read_buch_tabelle(conn)) }) }) diff --git a/R/entry_edit_modal.R b/R/entry_edit_modal.R index 6828ef1..1c5e3a9 100644 --- a/R/entry_edit_modal.R +++ b/R/entry_edit_modal.R @@ -1,105 +1,115 @@ +# module_posting.R -# --- MODUL UI --- postingModuleUI <- function(id) { ns <- NS(id) - tagList( - - ) + tagList() # Das UI wird dynamisch via showModal erzeugt } -# --- MODUL SERVER --- -postingModuleServer <- function(id, conn, trans_id, post_id = NULL) { +postingModuleServer <- function(id, conn, r_trans_id, r_trigger_list) { moduleServer(id, function(input, output, session) { - ns <- session$ns # Wichtig für das Namespacing innerhalb des Modals - if(is.null(post_id)){ - record <- read_posting(conn, trans_id) - }else { - wertstellung <- dbxSelect(conn, paste0("SELECT max(valuta) from Postings WHERE entry_id=", trans_id)) %>% - pull - record <- leer_df_from_table(conn, "postings") - record[1, "id"] <- post_id - record$entry_id <- trans_id - record$account_id <- 0 - record$valuta <- wertstellung - record$booking_date <- wertstellung - } - trans <- dbxSelect(conn, paste0("SELECT * FROM entries WHERE id=", record$entry_id)) - # Modal öffnen + ns <- session$ns - showModal(modalDialog( - title = "Buchung-Eingabe", + # Dieser Trigger signalisiert dem Hauptmodul: "Daten haben sich geändert!" + db_updated <- reactiveVal(0) + + # Hilfsvariablen für den aktuellen Edit-Status + current_record <- reactiveVal() + current_trans <- reactiveVal() + + # Beobachte den Trigger aus dem Hauptmodul + observeEvent(r_trigger_list(), { + req(r_trans_id()) + # 1. Daten laden + t_id <- r_trans_id() + p_id <- r_trigger_list()$post_id - f_airdatepicker_UI(ns("valuta"), "Wertstellung", record$valuta), - f_airdatepicker_UI(ns("buchungsdatum"), "Buchungsdatum", record$booking_date), - selectizeInput(ns("kontakt"), "Kontakt:",selected = trans$contact_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ünzen:", value = record$coin_share_amount, width = "100%") - ), - 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%") - ), - # - footer = tagList( - modalButton("Abbrechen"), - actionBttn(ns("confirm"), "Bestätigen", style = "minimal", color = "success"), - actionBttn(ns("delete"), "Löschen", style = "minimal", color = "danger"), - splitLayout(cellWidths = c("50%", "50%"), - numericInput(ns("bid"), "ID:", value = record$id), - numericInput(ns("trans_id"), "Trans_id:", value = record$entry_id) + trans_data <- dbxSelect(conn, paste0("SELECT * FROM entries WHERE id=", t_id)) + current_trans(trans_data) + + if (!is.null(p_id)) { + record <- read_posting(conn, p_id) + } else { + # Logik für neuen Eintrag + new_p_id <- max_id(conn, "postings") + 1 + wertstellung <- dbxSelect(conn, paste0("SELECT max(valuta) from Postings WHERE entry_id=", t_id)) %>% pull() + + record <- leer_df_from_table(conn, "postings") + record[1, "id"] <- new_p_id + record$entry_id <- t_id + record$account_id <- 0 + record$valuta <- wertstellung + record$booking_date <- wertstellung + } + current_record(record) + + # 2. Modal anzeigen + showModal(modalDialog( + title = paste("Buchung bearbeiten - Transaktion", t_id), + f_airdatepicker_UI(ns("valuta"), "Wertstellung", record$valuta), + f_airdatepicker_UI(ns("buchungsdatum"), "Buchungsdatum", record$booking_date), + selectizeInput(ns("kontakt"), "Kontakt:", selected = trans_data$contact_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 = record$project_id, 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ünzen:", value = record$coin_share_amount, width = "100%") ), - ), - easyClose = TRUE - )) - - # Aktion beim Bestätigen - observeEvent(input$confirm, ignoreInit = T, { - trans$contact_id <- input$kontakt - trans$note <- input$trans_notiz - record$amount <- input$amount - record$account_id <- input$konto - record$project_id <- input$projekt - record$valuta <- as.character(input$valuta) - record$booking_date <- as.character(input$buchungsdatum) - record$coin_share_amount <- input$coin_share_amount - record$receipt_id <- input$receipt_id - - # Check - Postings constraint - ## Entweder 1 oder 0 (neu) - anz <- dbxSelect(conn, paste0("SELECT count(id) FROM postings WHERE id=", record$id)) %>% pull - ok <- anz <=1 - ok <- !(is.na(record$entry_id) | record$entry_id == "") & ok - ok <- !(is.na(record$valuta) | record$valuta == "") & ok - ok <- !is.na(record$amount) - ok <- record$account_id > 0 & ok - - - if(ok) { - dbxUpsert(conn, "postings", records = record, where_cols = "id") - dbxUpsert(conn, "entries", records = trans, where_cols = "id") - removeModal() - return(reactive(input$confirm)) - } else showNotification("Die Buchung enthält Fehler", type = "error") - + textAreaInput(ns("trans_notiz"), label = "Notiz (Transaktion)", value = trans_data$note, width = "100%"), + + footer = tagList( + modalButton("Abbrechen"), + actionBttn(ns("delete"), "Löschen", style = "minimal", color = "danger"), + actionBttn(ns("confirm"), "Bestätigen", style = "minimal", color = "success") + ), + easyClose = TRUE + )) }) - # Aktion Detail löschen - observeEvent(input$delete, ignoreInit = T, { - anz <- dbxSelect(conn, paste0("SELECT count(entry_id) FROM postings WHERE entry_id=", record$entry_id) ) %>% pull - if(anz>1){ - dbxDelete(conn, "postings", where = data.frame(id=record$id)) - removeModal() - return(reactive(input$delete)) + # Speichern + observeEvent(input$confirm, { + req(current_record(), current_trans()) + + # Daten aus UI einsammeln + rec <- current_record() + tra <- current_trans() + + tra$contact_id <- input$kontakt + tra$note <- input$trans_notiz + rec$amount <- input$amount + rec$account_id <- input$konto + rec$project_id <- input$projekt + rec$valuta <- as.character(input$valuta) + rec$booking_date <- as.character(input$buchungsdatum) + rec$coin_share_amount <- input$coin_share_amount + + # Validierung + ok <- !is.na(rec$amount) && rec$account_id > 0 + + if (ok) { + dbxUpsert(conn, "postings", records = rec, where_cols = "id") + dbxUpsert(conn, "entries", records = tra, where_cols = "id") + removeModal() + db_updated(db_updated() + 1) # Hauptmodul triggern } else { - showNotification("Das ist der letzte Buchungsteil, er kann nicht gelöscht werden -> Transaktion löschen", type = "error") + showNotification("Eingabe ungültig", type = "error") } }) - # + # Löschen + observeEvent(input$delete, { + rec <- current_record() + anz <- dbxSelect(conn, paste0("SELECT count(*) FROM postings WHERE entry_id=", rec$entry_id)) %>% pull() + + if (anz > 1) { + dbxDelete(conn, "postings", where = data.frame(id = rec$id)) + removeModal() + db_updated(db_updated() + 1) + } else { + showNotification("Letzter Teil kann nicht gelöscht werden.", type = "error") + } + }) + + return(db_updated) }) } \ No newline at end of file diff --git a/db/development.sqlite b/db/development.sqlite index 2df833be00b35b4da8c890bdb52e2503e30669a3..b27b36bb00723f8667982c1b1befdd8d07008f10 100644 GIT binary patch delta 545 zcmZp8VB7G(c7imc(nJ|&Mx~7j$@%Qg3=9k!k0hE;Z8|SKVFfd69>Z__U z?hY=jnll zKyFt%Qx%7c0{d!4z7jrN-WR+Jd1H7*c+T;(@i=h*z`3k81{302deMA+g)WZ7@<@8mDxciohI)df z!s$^A#1$(Vn9i`Z@0!O1#LPg<0>rF93=(4pVh$kY1Y#~A<_2OOAm-h^YaXB5MF5R1 Bs4V~h delta 352 zcmZp8VB7G(c7imc;TYWGLk9;Pm4Z;5f(8%b~-5hkXWn2)i8HL$+mXk!(_| zmsw}B#= lGZ3=?F)I+W0Wmuea{w_X5OV=BHxTmxG4J+W^Z48@0s!*ia>W1u diff --git a/server.R b/server.R index a1cfc38..f5a7f29 100644 --- a/server.R +++ b/server.R @@ -1,5 +1,5 @@ server <- function(input, output) { - buchungenServer("buchungen_tab") + buchungenServer("buchungen_tab", conn) }