From a0e2fb986dfee8d504375fbc2bfbd2da8d703b83 Mon Sep 17 00:00:00 2001 From: Christian Oswald Date: Tue, 28 Apr 2026 15:40:44 +0200 Subject: [PATCH] =?UTF-8?q?verschiedene=20=C3=A4nderungen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- R/_funktionen/f_table.R | 5 +- R/contacts/module_contacts.R | 1 + R/postings/buchungen_mod.R | 4 +- R/umsatz/module_umsatz.R | 125 +++++++++++++++++++++++++---------- 4 files changed, 97 insertions(+), 38 deletions(-) diff --git a/R/_funktionen/f_table.R b/R/_funktionen/f_table.R index 778d5b7..a769c00 100755 --- a/R/_funktionen/f_table.R +++ b/R/_funktionen/f_table.R @@ -1,5 +1,5 @@ f_reactable <- function(daten, coldefs = NULL, selection = "single", - defaultSelected = NULL, hoehe = NULL, highlight_valuta = NULL) { + defaultSelected = NULL, hoehe = NULL, highlight_valuta = NULL, filterable = T) { reactable( daten, selection = selection, @@ -7,7 +7,8 @@ f_reactable <- function(daten, coldefs = NULL, selection = "single", pagination = F, defaultPageSize = 17, showPageSizeOptions = TRUE, - filterable = TRUE, + defaultSorted = list(valuta = "desc"), + filterable = filterable, highlight = TRUE, height = hoehe, # <--- FIXIERT DEN HEADER und macht den Body scrollbar bordered = TRUE, diff --git a/R/contacts/module_contacts.R b/R/contacts/module_contacts.R index 5f58522..f16aeae 100644 --- a/R/contacts/module_contacts.R +++ b/R/contacts/module_contacts.R @@ -119,6 +119,7 @@ contactsServer <- function(id, conn, r_global) { valuta = colDef(name = "Wertstellung", minWidth = 80), account_name = colDef( name = "Konto", minWidth = 150 ), projektname = colDef(name = "Projekt", minWidth = 150), + amount = colDef(name = "Betrag", minWidth = 150) ) ) diff --git a/R/postings/buchungen_mod.R b/R/postings/buchungen_mod.R index b1987b1..73c71a3 100755 --- a/R/postings/buchungen_mod.R +++ b/R/postings/buchungen_mod.R @@ -97,6 +97,7 @@ buchungenServer <- function(id, conn, r_global) { selection = "single", hoehe = "60vh", defaultSelected = current_main_idx(), + filterable = TRUE, highlight_valuta = highlighted_valuta() ) }) @@ -119,7 +120,8 @@ buchungenServer <- function(id, conn, r_global) { details_data(), coldefs = coldef_entries_tabelle, selection = "single", - hoehe = NULL + hoehe = NULL, + filterable = FALSE ) }) diff --git a/R/umsatz/module_umsatz.R b/R/umsatz/module_umsatz.R index 6dd6a33..d8cea26 100755 --- a/R/umsatz/module_umsatz.R +++ b/R/umsatz/module_umsatz.R @@ -4,11 +4,15 @@ umsatzUI <- function(id) { useShinyjs(), # Sync-Button oben div( - style = "display: flex; justify-content: flex-end; margin-bottom: 8px;", + style = "display: flex; justify-content: flex-end; margin-bottom: 8px; gap: 8px;", + actionBttn(ns("deselect_all"), "Auswahl aufheben", + size = "xs", style = "minimal", + icon = icon("xmark"), color = "default"), actionBttn(ns("sync"), "Sync Hibiscus", size = "xs", style = "minimal", icon = icon("rotate"), color = "primary") ), + # Buchungspanel immer sichtbar unten uiOutput(ns("buchungs_panel")), # Tabelle mit begrenzter Höhe und Scroll @@ -78,7 +82,7 @@ umsatzServer <- function(id, conn, r_global) { }"), filterable = F, pagination = F, - selection = "single", + selection = "multiple", onClick = "select", defaultSorted = list(valuta = "desc"), columns = list( @@ -128,18 +132,46 @@ umsatzServer <- function(id, conn, r_global) { # ── Buchungs-Panel ---- output$buchungs_panel <- renderUI({ req(selected_umsatz()) - u <- selected_umsatz() + rows <- selected_umsatz() - if (u$gebucht) { + alle_gebucht <- all(rows$gebucht) + manche_gebucht <- any(rows$gebucht) && !alle_gebucht + keine_gebucht <- !any(rows$gebucht) + + if (alle_gebucht && nrow(rows) == 1) { + # Einzelauswahl, bereits gebucht → Sprung-Button tagList( h4("Bereits gebucht"), actionBttn(ns("goto_buchung"), - paste0("→ Zur Buchung (Entry #", u$entry_id, ")"), + paste0("→ Zur Buchung (Entry #", rows$entry_id, ")"), size = "sm", style = "minimal", color = "primary") ) + } else if (manche_gebucht) { + div(style = "color: orange;", + icon("triangle-exclamation"), + strong(paste0(sum(rows$gebucht), " von ", nrow(rows), + " Umsätzen bereits gebucht – bitte nur ungebuchte auswählen.")) + ) } else { + # Alle ungebucht → Buchungsformular + info_text <- if (nrow(rows) > 1) { + tags$p(style = "color: steelblue; font-size: 0.9em;", + icon("info-circle"), + paste0(nrow(rows), " Umsätze ausgewählt – ", + "Konto/Projekt/Kontakt werden für alle verwendet.") + ) + } + + # Kontakt: bei Mehrfachauswahl keinen Auto-Resolve + kontakt_selected <- if (nrow(rows) == 1) { + resolve_contact(conn, rows$empfaenger_name, rows$empfaenger_konto) + } else { + NULL + } + tagList( - h4("Buchen"), + h4(if (nrow(rows) == 1) "Buchen" else paste0(nrow(rows), " Umsätze buchen")), + info_text, fluidRow( column(4, selectizeInput(ns("konto"), "Konto:", @@ -155,11 +187,11 @@ umsatzServer <- function(id, conn, r_global) { column(3, selectizeInput(ns("kontakt"), "Kontakt:", choices = get_contact_choices(conn), - selected = resolve_contact(conn, u$empfaenger_name, u$empfaenger_konto), + selected = kontakt_selected, width = "100%") ), column(1, - div(style = "margin-top: 25px;", # Label-Höhe ausgleichen + div(style = "margin-top: 25px;", actionBttn(ns("new_contact"), "Neuer Kontakt", size = "xs", style = "minimal", icon = icon("plus"), color = "warning") ) @@ -171,7 +203,7 @@ umsatzServer <- function(id, conn, r_global) { icon = icon("check")) ) ) - ) + ) ) } }) @@ -186,36 +218,55 @@ umsatzServer <- function(id, conn, r_global) { # ── Buchen ---- observeEvent(input$buchen, { - req(selected_umsatz(), input$konto) - u <- selected_umsatz() + req(selected_umsatz(), input$konto, input$konto != 0) + rows <- selected_umsatz() + rows <- rows %>% filter(!gebucht) + req(nrow(rows) > 0) - gegenkonto_id <- dbReadTable(conn, "accounts") %>% - filter(hibiscus_account_id == u$konto_id) %>% - pull(id) + bank_connections <- dbReadTable(conn, "bank_connections") - req(length(gegenkonto_id) > 0, input$konto != 0) - - new_t_id <- max_id(conn, "entries") + 1L - dbxInsert(conn, "entries", data.frame( - id = new_t_id, - contact_id = as.integer(input$kontakt), - note = u$zweck - )) - - p_id1 <- max_id(conn, "postings") + 1L - dbxInsert(conn, "postings", data.frame( - id = c(p_id1, p_id1 + 1L), - entry_id = c(new_t_id, new_t_id), - account_id = c(as.integer(input$konto), gegenkonto_id), - project_id = c(as.integer(input$projekt), NA_integer_), - amount = c(u$betrag, -u$betrag), - valuta = c(as.character(u$valuta), as.character(u$valuta)), - booking_date = c(as.character(u$datum), as.character(u$datum)), - bank_transaction_id = c(u$id, NA_integer_) - )) + for (i in seq_len(nrow(rows))) { + u <- rows[i, ] + gegenkonto_id <- dbReadTable(conn, "accounts") %>% + filter(hibiscus_account_id == u$konto_id) %>% + pull(id) + if (length(gegenkonto_id) == 0) next + + # contact_id: manuell gewählt oder aus bank_connections + contact_id <- if (!is.null(input$kontakt) && as.integer(input$kontakt) != 0) { + as.integer(input$kontakt) + } else { + match <- bank_connections %>% + filter(contact_text == u$empfaenger_name) %>% + pull(contact_id) + if (length(match) > 0) as.integer(match[[1]]) else NA_integer_ + } + + new_t_id <- max_id(conn, "entries") + 1L + dbxInsert(conn, "entries", data.frame( + id = new_t_id, + contact_id = contact_id, + note = u$zweck + )) + + p_id1 <- max_id(conn, "postings") + 1L + dbxInsert(conn, "postings", data.frame( + id = c(p_id1, p_id1 + 1L), + entry_id = c(new_t_id, new_t_id), + account_id = c(as.integer(input$konto), gegenkonto_id), + project_id = c(as.integer(input$projekt), NA_integer_), + amount = c(u$betrag, -u$betrag), + valuta = c(as.character(u$valuta), as.character(u$valuta)), + booking_date = c(as.character(u$datum), as.character(u$datum)), + bank_transaction_id = c(u$id, NA_integer_) + )) + } refresh(refresh() + 1) - showNotification("✓ Gebucht", type = "message") + showNotification( + paste0("✓ ", nrow(rows), " Umsatz/Umsätze gebucht"), + type = "message" + ) }) # ── Neuer Kontakt / Bankverbindung ---- @@ -277,6 +328,10 @@ umsatzServer <- function(id, conn, r_global) { showNotification(msg, type = "message") }) + ## ** Auswahl aufheben ---- + observeEvent(input$deselect_all, { + updateReactable("umsatz_table", selected = NA, session = session) + }) }) }