Compare commits
9 Commits
d0a5cebdcd
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 12ef7693d5 | |||
| 0bbb656d22 | |||
| 1f0b9eb214 | |||
| 23e3c3d4fb | |||
| a0e2fb986d | |||
| db63fecded | |||
| 089a930488 | |||
| f16386810f | |||
| 85ad80add7 |
@@ -5,3 +5,4 @@
|
||||
.Ruserdata
|
||||
www/attachments/
|
||||
|
||||
.positai
|
||||
|
||||
+29
-2
@@ -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,
|
||||
@@ -32,4 +33,30 @@ f_reactable <- function(daten, coldefs = NULL, selection = "single",
|
||||
onClick = "select",
|
||||
columns = coldefs
|
||||
)
|
||||
}
|
||||
|
||||
f_reactable_sub <- function(daten, coldefs = NULL) {
|
||||
reactable(
|
||||
daten,
|
||||
selection = "single",
|
||||
pagination = T,
|
||||
defaultPageSize = 17,
|
||||
showPageSizeOptions = TRUE,
|
||||
filterable = TRUE,
|
||||
highlight = TRUE,
|
||||
bordered = TRUE,
|
||||
striped = FALSE,
|
||||
compact = TRUE,
|
||||
# Styling
|
||||
theme = reactableTheme(
|
||||
highlightColor = "#e6f7ff", # Etwas dezenter als knallgrün, optional
|
||||
# borderColor = "#dfe2e5",
|
||||
rowSelectedStyle = list(backgroundColor = "#98F5FF")#
|
||||
),
|
||||
rowStyle = function(index) {
|
||||
style <- list(cursor = "pointer") # immer aktiv
|
||||
},
|
||||
onClick = "select",
|
||||
columns = coldefs
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
##
|
||||
## Datum: 2024-10-17_14-31
|
||||
## Name: Christian Oswald
|
||||
## Projekt: etatverwaltung
|
||||
## Datei: f_zeitstempel.R
|
||||
## Kommentar: Erstellt einen Zeitstempel für die DB-Felder updatea und created
|
||||
##
|
||||
|
||||
# Funkton die prüft ob das Format stimmt
|
||||
is.ymd_hms <- function(x) !is.na(lubridate::ymd_hms(x, quiet = TRUE))
|
||||
# Zeitstempel
|
||||
f_zeitstempel <- function(ts){
|
||||
# Erstellt einen wenn leer
|
||||
if(is.na(ts)) ts <- format(Sys.time(), tz="")
|
||||
# Erstellt einen wenn das Format nicht stimmt
|
||||
if(!is.ymd_hms(ts)) ts <- format(Sys.time(), tz="")
|
||||
return(ts)
|
||||
}
|
||||
@@ -6,7 +6,7 @@ sync_hibiscus <- function(conn) {
|
||||
error_f <- NULL
|
||||
|
||||
# ── Einstellungen ────────────────────────────────────────────────────────────
|
||||
SYNC_AB <- as.Date("2024-01-01")
|
||||
SYNC_AB <- as.Date("2026-01-01")
|
||||
|
||||
if (as.character(Sys.info())[1] == "Darwin") {
|
||||
h2jar <- "/Users/cosw/bin/jameica.app/lib/h2/migration-h2/disabled/h2-1.4.199.jar"
|
||||
|
||||
@@ -80,7 +80,6 @@ accountsServer <- function(id, conn, r_global) {
|
||||
# ── Gewähltes Konto ────────────────────────────────────────────────────────
|
||||
selected_name <- reactive({
|
||||
sel <- get_selected(input$account_tree, format = "names")
|
||||
cat("raw selected:", paste(sel, collapse=", "), "\n")
|
||||
if (length(sel) == 0) return(NULL)
|
||||
name <- sub(" \\(\\d+\\)$", "", sel[[1]])
|
||||
cat("cleaned name:", name, "\n")
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
##
|
||||
## Datum : 2026-04-28_13-43
|
||||
## Name : Christian Oswald
|
||||
## Datei : contact_io.R
|
||||
## Projekt : gemfin-shiny
|
||||
## Kommentar: Befüllt die Felder und liest sie aus
|
||||
##
|
||||
|
||||
contact_io <- function(session, input, output, record, flag){
|
||||
if(flag == "lesen"){
|
||||
record[1,"id"] <- input$cid
|
||||
record$first_name <- input$first_name
|
||||
record$last_name <- input$last_name
|
||||
record$postal_code <- input$postal_code
|
||||
record$city <- input$city
|
||||
record$street <- input$street
|
||||
record$phone <- input$phone
|
||||
record$mobile <- input$mobile
|
||||
record$email <- input$email
|
||||
record$member <- input$member
|
||||
record$display_name <- input$display_name
|
||||
record$is_company <- input$is_company
|
||||
record$notes <- input$note
|
||||
record$created_at <- f_zeitstempel( record$created_at)
|
||||
record$updated_at <- format(Sys.time(), tz="")
|
||||
return(record)
|
||||
}
|
||||
if(flag == "schreiben"){
|
||||
updateNumericInput(session,"cid", value = record$id)
|
||||
updateTextInput(session,"display_name", value = record$display_name)
|
||||
updateTextInput(session,"first_name", value = record$first_name)
|
||||
updateTextInput(session,"last_name", value = record$last_name)
|
||||
updateTextInput(session,"postal_code", value = record$postal_code)
|
||||
updateTextInput(session,"city", value = record$city)
|
||||
updateTextInput(session,"email", value = record$email)
|
||||
updateTextInput(session,"street", value = record$street)
|
||||
updateTextInput(session,"mobile", value = record$mobile)
|
||||
updateTextInput(session,"phone", value = record$phone)
|
||||
updateTextAreaInput(session,"note", value = record$notes)
|
||||
updateCheckboxInput(session, "is_company", value = record$is_company)
|
||||
updateCheckboxInput(session, "member", value = record$member)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
##
|
||||
## Datum : 2026-04-28_13-11
|
||||
## Name : Christian Oswald
|
||||
## Datei : contacts.R
|
||||
## Projekt : gemfin-shiny
|
||||
## Kommentar: model für contacts
|
||||
##
|
||||
|
||||
|
||||
f_contacts_tabelle <- function(conn){
|
||||
tbl(conn, "contacts") %>%
|
||||
select(id, display_name, street, city, member) %>%
|
||||
arrange(display_name) %>%
|
||||
collect
|
||||
}
|
||||
|
||||
f_contact <- function(conn, idwert){
|
||||
dbxSelect(conn, paste0("SELECT * FROM contacts WHERE id=", idwert))
|
||||
}
|
||||
|
||||
get_contact_choices <- function(conn) {
|
||||
contacts <- tbl(conn, "contacts") |>
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
contactsUI <- function(id) {
|
||||
ns <- NS(id)
|
||||
tagList(
|
||||
useShinyjs(),
|
||||
# Sync-Button oben
|
||||
div(
|
||||
style = "display: flex; justify-content: flex-end; margin-bottom: 8px;",
|
||||
actionBttn(ns("sync"), "Sync Hibiscus",
|
||||
size = "xs", style = "minimal",
|
||||
icon = icon("rotate"), color = "primary")
|
||||
),
|
||||
sidebarLayout(
|
||||
sidebarPanel(width = 6,
|
||||
reactableOutput(ns("contacts_table"), height = 800)
|
||||
|
||||
),
|
||||
mainPanel(width = 6,
|
||||
fluidRow(
|
||||
column(8, textInput(ns("display_name"), label = "Bezeichnung", value = NA, width = "100%")),
|
||||
column(4, numericInput(ns("cid"), label = "ID", value = NA, width = "100%"))
|
||||
),
|
||||
fluidRow(
|
||||
column(6, textInput(ns("first_name"), label = "Vorname", value = NA, width = "100%")),
|
||||
column(6, textInput(ns("last_name"), label = "Nachname", value = NA, width = "100%"))
|
||||
),
|
||||
fluidRow(
|
||||
column(12, textInput(ns("street"), label = "Strasse", value = NA, width = "100%"))
|
||||
),
|
||||
fluidRow(
|
||||
column(4, textInput(ns("postal_code"), label = "PLZ", value = NA, width = "100%")),
|
||||
column(8, textInput(ns("city"), label = "Ort", value = NA, width = "100%"))
|
||||
),
|
||||
fluidRow(
|
||||
column(4, textInput(ns("phone"), label = "Telefon", value = NA, width = "100%")),
|
||||
column(4, textInput(ns("mobile"), label = "Mobil", value = NA, width = "100%")),
|
||||
column(4, textInput(ns("email"), label = "E-Mail", value = NA, width = "100%"))
|
||||
),
|
||||
fluidRow(
|
||||
column(4, checkboxInput(ns("is_company"), label = "Firma", value = F, width = "100%")),
|
||||
column(4, checkboxInput(ns("member"), label = "Mitglied", value = F, width = "100%"))
|
||||
),
|
||||
fluidRow(
|
||||
column(12, textAreaInput(ns("note"), label = "Notiz", value = NA, width = "100%"))
|
||||
),
|
||||
fluidRow(
|
||||
column(4, actionBttn(ns("add"), "Neu", size = "sm", style = "material-flat", color = "warning", icon = icon("plus"), block = T)),
|
||||
column(4, actionBttn(ns("del"), "Löschen", size = "sm", style = "material-flat", color = "danger", icon = icon("trash"), block = T)),
|
||||
column(4, actionBttn(ns("save"), "Speichern", size = "sm", style = "material-flat", color = "success", icon = icon("floppy-disk"), block = T)),
|
||||
),
|
||||
br(),
|
||||
reactableOutput(ns("entries"))
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
contactsServer <- function(id, conn, r_global) {
|
||||
moduleServer(id, function(input, output, session) {
|
||||
ns <- session$ns
|
||||
|
||||
# ── Daten ----
|
||||
refresh <- reactiveVal(0)
|
||||
zeige_alle <- reactiveVal(FALSE)
|
||||
|
||||
contact_data <- reactive({
|
||||
f_contacts_tabelle(conn)
|
||||
})
|
||||
|
||||
|
||||
# ── Filter-Buttons ----
|
||||
observeEvent(input$mitglieder, { zeige_alle(FALSE) })
|
||||
observeEvent(input$filter_alle, { zeige_alle(TRUE) })
|
||||
|
||||
# ── Tabelle ----
|
||||
output$contacts_table <- renderReactable({
|
||||
reactable(
|
||||
contact_data(),
|
||||
striped = TRUE,
|
||||
highlight = TRUE,
|
||||
filterable = T,
|
||||
pagination = F,
|
||||
selection = "single",
|
||||
onClick = "select",
|
||||
defaultSorted = list(display_name = "asc"),
|
||||
columns = list(
|
||||
id = colDef(show = FALSE),
|
||||
display_name = colDef( name = "Bezeichnung", minWidth = 150 ),
|
||||
street = colDef(name = "Strasse", minWidth = 150),
|
||||
city = colDef(name = "Ort", minWidth = 150),
|
||||
member = colDef(name = "Mitglied", maxWidth = 80,
|
||||
align = "center")
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
## * Selektierter Umsatz ----
|
||||
sel <- reactive(getReactableState("contacts_table", "selected"))
|
||||
observeEvent(sel(), ignoreInit = T, {
|
||||
idwert <- contact_data()$id[sel()]
|
||||
record <- f_contact(conn, idwert)
|
||||
contact_io(session, input, output, record, "schreiben")
|
||||
# ── Buchungs-Panel ----
|
||||
entries <- read_buch_tabelle(conn) %>%
|
||||
filter(contact_id == idwert) %>%
|
||||
select(id, valuta, account_name, projektname, amount)
|
||||
output$entries <- renderReactable(
|
||||
reactable(entries,
|
||||
striped = TRUE,
|
||||
highlight = TRUE,
|
||||
searchable = T,
|
||||
filterable = F,
|
||||
pagination = T,
|
||||
defaultPageSize = 6,
|
||||
selection = "single",
|
||||
onClick = "select",
|
||||
columns = list(
|
||||
id = colDef(show = FALSE),
|
||||
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)
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
## * Speichern ----
|
||||
observeEvent(input$save, ignoreInit = T, {
|
||||
record <- leer_df_from_table(conn, "contacts")
|
||||
record <- contact_io(session, input, output, record, "lesen")
|
||||
print(record)
|
||||
dbxUpsert(conn, "contacts", records = record, where_cols = c("id"))
|
||||
})
|
||||
|
||||
## * Hinzufügen ----
|
||||
observeEvent(input$add, ignoreInit = T, {
|
||||
record <- leer_df_from_table(conn, "contacts")
|
||||
record$id <- max_id(conn, "contacts")
|
||||
record <- contact_io(session, input, output, record, "schreiben")
|
||||
})
|
||||
## * Löschen ----
|
||||
observeEvent(input$del, ignoreInit = T, {
|
||||
dbxDelete(conn, "contacts", where = data.frame(id = input$cid))
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ── Zur Buchung springen ----
|
||||
# observeEvent(input$goto_buchung, {
|
||||
# req(selected_umsatz())
|
||||
# r_global$nav_history <- c(r_global$nav_history, list(list(tab = "umsatz")))
|
||||
# r_global$jump_to_entry_id <- selected_umsatz()$entry_id
|
||||
# r_global$active_tab <- "buchungen"
|
||||
# })
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
@@ -22,7 +22,7 @@ read_buch_tabelle <- function(conn, trans_id = NULL){
|
||||
left_join(accounts, by = c("account_id" = "id")) |>
|
||||
left_join(projects, by = c("project_id" = "id")) |>
|
||||
left_join(attachment_counts, by = "entry_id") |>
|
||||
select(id, valuta, account_name, projektname, display_name, amount, entry_id, n_attachments, account_id) |>
|
||||
select(id, valuta, account_name, projektname, display_name, amount, entry_id, n_attachments, account_id, contact_id) |>
|
||||
collect() %>%
|
||||
mutate(
|
||||
saldo = 0,
|
||||
@@ -35,6 +35,11 @@ read_posting <- function(conn, id){
|
||||
dbxSelect(conn, paste0("SELECT * FROM postings WHERE id =", id))
|
||||
}
|
||||
|
||||
read_buch_entries <- function(conn, idwert){
|
||||
dbxSelect(conn, paste0("SELECT id, entry_id, valuta, account_id, project_id, amount FROM postings WHERE entry_id=", idwert))
|
||||
|
||||
}
|
||||
|
||||
|
||||
coldef_entries_tabelle <-
|
||||
list(
|
||||
|
||||
+113
-136
@@ -5,22 +5,46 @@ buchungenUI <- function(id) {
|
||||
tagList(
|
||||
useShinyjs(),
|
||||
h3("Hauptbuchungen"),
|
||||
reactableOutput(ns("buchungen_table")),
|
||||
hr(),
|
||||
h3("Details / Gegenbuchungen"),
|
||||
reactableOutput(ns("details_table")),
|
||||
h3("Anhänge"),
|
||||
uiOutput(ns("attachments_ui")),
|
||||
fileInput(ns("anhang"), NULL, multiple = TRUE,
|
||||
accept = c(".pdf", ".jpg", ".png", ".xlsx", ".docx"),
|
||||
width = "100%"),
|
||||
br(),
|
||||
actionBttn(ns("add_trans"), "Transaktion hinzu", size = "sm", style = "material-flat", color = "warning"),
|
||||
actionBttn(ns("del_trans"), "Transaktion Löschen", size = "sm", style = "material-flat", color = "danger"),
|
||||
actionBttn(ns("add_detail"), "Detail hinzu", size = "sm", style = "material-flat", color = "success"),
|
||||
fluidRow(
|
||||
column(8, reactableOutput(ns("buchungen_table")),
|
||||
hr(),
|
||||
h3("Details / Gegenbuchungen"),
|
||||
uiOutput(ns("details_table")) ,
|
||||
|
||||
|
||||
),
|
||||
|
||||
column(4,
|
||||
selectizeInput(ns("contact"), label = "Kontakt", choices = get_contact_choices(conn),
|
||||
selected = NA, width = "100%"),
|
||||
textAreaInput(ns("note"), label = "Verwendungszweck", value = NA, width = "100%"),
|
||||
h3("Dateien"),
|
||||
uiOutput(ns("attachments_ui")),
|
||||
fileInput(ns("anhang"), NULL, multiple = TRUE,
|
||||
accept = c(".pdf", ".jpg", ".png", ".xlsx", ".docx"),
|
||||
width = "100%"),
|
||||
div(
|
||||
style = "display: flex; flex-direction: column; gap: 10px; align-items: flex-start;",
|
||||
|
||||
actionBttn(ns("add_detail"), "Detail hinzu",
|
||||
size = "sm", style = "material-flat", color = "default", block = TRUE),
|
||||
actionBttn(ns("del_detail"), "Detail Löschen",
|
||||
size = "sm", style = "material-flat", color = "danger", block = TRUE),
|
||||
actionBttn(ns("add_trans"), "Transaktion hinzu",
|
||||
size = "sm", style = "material-flat", color = "default", block = TRUE),
|
||||
|
||||
actionBttn(ns("del_trans"), "Transaktion Löschen",
|
||||
size = "sm", style = "material-flat", color = "danger", block = TRUE),
|
||||
actionBttn(ns("save_trans"), "Transaktion speichern",
|
||||
size = "sm", style = "material-flat", color = "success", block = TRUE, icon = icon("floppy-disk")),
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Das UI-Element des Moduls (auch wenn es leer ist)
|
||||
postingModuleUI(ns("posting_modal"))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,20 +52,17 @@ buchungenServer <- function(id, conn, r_global) {
|
||||
moduleServer(id, function(input, output, session) {
|
||||
ns <- session$ns
|
||||
|
||||
# ── Reactive Variablen ─────────────────────────────────────────────────────
|
||||
# ── Reactive Variablen ----
|
||||
postings_data <- reactiveVal(read_buch_tabelle(conn))
|
||||
details_data <- reactiveVal(NULL)
|
||||
sel_details <- reactiveVal(NULL)
|
||||
selected_trans_id <- reactiveVal(NULL)
|
||||
current_main_idx <- reactiveVal(NULL)
|
||||
reset_trigger <- reactiveVal(0)
|
||||
modal_trigger <- reactiveVal(list(post_id = NULL, counter = 0))
|
||||
highlighted_valuta <- reactiveVal(NULL)
|
||||
reset_trigger <- reactiveVal(NULL)
|
||||
current_main_idx <- reactiveVal(NULL)
|
||||
|
||||
update_db_trigger <- postingModuleServer(
|
||||
"posting_modal", conn, selected_trans_id, modal_trigger
|
||||
)
|
||||
|
||||
# ── Filter ─────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
# ── Filter ----
|
||||
aktiver_filter <- reactive(r_global$buchungen_filter)
|
||||
|
||||
gefilterte_daten <- reactive({
|
||||
@@ -58,37 +79,8 @@ buchungenServer <- function(id, conn, r_global) {
|
||||
else d
|
||||
})
|
||||
|
||||
observeEvent(aktiver_filter(), {
|
||||
current_main_idx(NULL)
|
||||
selected_trans_id(NULL)
|
||||
details_data(NULL)
|
||||
})
|
||||
|
||||
# ── Sprung von anderem Modul ───────────────────────────────────────────────
|
||||
observeEvent(r_global$jump_to_entry_id, ignoreInit = TRUE, {
|
||||
req(r_global$jump_to_entry_id)
|
||||
t_id <- r_global$jump_to_entry_id
|
||||
|
||||
# Daten neu laden
|
||||
postings_data(read_buch_tabelle(conn))
|
||||
|
||||
# Zeile suchen
|
||||
idx <- which(gefilterte_daten()$entry_id == t_id)[1]
|
||||
req(!is.na(idx))
|
||||
|
||||
current_main_idx(idx)
|
||||
selected_trans_id(t_id)
|
||||
details_data(read_buch_tabelle(conn, trans_id = t_id))
|
||||
highlighted_valuta(gefilterte_daten()[idx, "valuta"])
|
||||
|
||||
reset_trigger(isolate(reset_trigger()) + 1)
|
||||
scroll_to_row(ns("buchungen_table"), idx)
|
||||
|
||||
# Sprungziel zurücksetzen
|
||||
r_global$jump_to_entry_id <- NULL
|
||||
})
|
||||
|
||||
# ── Haupttabelle rendern ───────────────────────────────────────────────────
|
||||
# ── Haupttabelle rendern ----
|
||||
output$buchungen_table <- renderReactable({
|
||||
reset_trigger()
|
||||
f_reactable(
|
||||
@@ -96,99 +88,84 @@ buchungenServer <- function(id, conn, r_global) {
|
||||
coldefs = coldef_entries_tabelle,
|
||||
selection = "single",
|
||||
hoehe = "60vh",
|
||||
defaultSelected = current_main_idx(),
|
||||
highlight_valuta = highlighted_valuta()
|
||||
filterable = TRUE,
|
||||
)
|
||||
})
|
||||
|
||||
# ── Details laden ──────────────────────────────────────────────────────────
|
||||
sel_details <- reactive(getReactableState("buchungen_table", "selected"))
|
||||
sel <- reactive(getReactableState("buchungen_table", "selected"))
|
||||
observeEvent(sel(), ignoreInit = T, {
|
||||
output$details_table <- renderUI({
|
||||
req(sel())
|
||||
# Daten laden
|
||||
df <- read_buch_entries(conn, gefilterte_daten()$entry_id[sel()])
|
||||
sel_details(df)
|
||||
|
||||
rows <- lapply(1:nrow(df), function(ind) {
|
||||
fluidRow(class = "details-row",
|
||||
# HIER: Index an die ID hängen!
|
||||
column(1, numericInput(ns(paste0("id_", ind)), label = NULL, width = "100%", value = df$id[ind])),
|
||||
column(2, dateInput(ns(paste0("valuta_", ind)), label = NULL, width = "100%", value = df$valuta[ind])),
|
||||
column(3, selectizeInput(ns(paste0("account_", ind)), label = NULL,
|
||||
choices = get_account_choices(conn),
|
||||
selected = df$account_id[ind], width = "100%")),
|
||||
column(3, selectizeInput(ns(paste0("projekt_", ind)), label = NULL,
|
||||
choices = get_project_choices(conn),
|
||||
selected = df$projekt_id[ind], width = "100%")),
|
||||
column(2, numericInput(ns(paste0("betrag_", ind)), label = NULL, width = "100%", value = df$amount[ind]))
|
||||
)
|
||||
})
|
||||
tagList(rows)
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
||||
observeEvent(sel_details(), ignoreInit = TRUE, {
|
||||
observeEvent(input$save_trans, {
|
||||
req(sel_details())
|
||||
highlighted_valuta(gefilterte_daten()[sel_details(), "valuta"])
|
||||
current_main_idx(sel_details())
|
||||
t_id <- gefilterte_daten()[sel_details(), "entry_id"] |> pull()
|
||||
selected_trans_id(t_id)
|
||||
details_data(read_buch_tabelle(conn, trans_id = t_id))
|
||||
})
|
||||
|
||||
output$details_table <- renderReactable({
|
||||
req(details_data())
|
||||
f_reactable(
|
||||
details_data(),
|
||||
coldefs = coldef_entries_tabelle,
|
||||
selection = "single",
|
||||
hoehe = NULL
|
||||
)
|
||||
})
|
||||
|
||||
# ── Detail hinzufügen ──────────────────────────────────────────────────────
|
||||
observeEvent(input$add_detail, {
|
||||
req(selected_trans_id())
|
||||
modal_trigger(list(post_id = NULL, counter = modal_trigger()$counter + 1))
|
||||
})
|
||||
|
||||
# ── Detail editieren ───────────────────────────────────────────────────────
|
||||
sel_detail <- reactive(getReactableState("details_table", "selected"))
|
||||
|
||||
observeEvent(sel_detail(), ignoreInit = TRUE, {
|
||||
p_id <- details_data()$id[sel_detail()]
|
||||
modal_trigger(list(post_id = p_id, counter = modal_trigger()$counter + 1))
|
||||
})
|
||||
|
||||
# ── DB-Update durch Modal ──────────────────────────────────────────────────
|
||||
observeEvent(update_db_trigger(), ignoreInit = TRUE, {
|
||||
req(selected_trans_id())
|
||||
details_data(read_buch_tabelle(conn, trans_id = selected_trans_id()))
|
||||
postings_data(read_buch_tabelle(conn))
|
||||
updateReactable("buchungen_table", data = gefilterte_daten())
|
||||
})
|
||||
|
||||
# ── Neue Transaktion ───────────────────────────────────────────────────────
|
||||
observeEvent(input$add_trans, {
|
||||
new_t_id <- max_id(conn, "entries") + 1
|
||||
dbxInsert(conn, "entries", data.frame(id = new_t_id))
|
||||
p_id1 <- max_id(conn, "postings") + 1
|
||||
p_id2 <- p_id1 + 1
|
||||
dbxInsert(conn, "postings", data.frame(
|
||||
id = c(p_id1, p_id2),
|
||||
entry_id = c(new_t_id, new_t_id),
|
||||
amount = c(0, 0),
|
||||
account_id = c(0, 0),
|
||||
valuta = c(Sys.Date(), Sys.Date())
|
||||
))
|
||||
n_rows <- nrow(sel_details())
|
||||
|
||||
# Filter zurücksetzen damit neue Transaktion sichtbar ist
|
||||
r_global$buchungen_filter <- "alle"
|
||||
# Alle Zeilen in einer Liste sammeln
|
||||
all_records <- lapply(1:n_rows, function(ind) {
|
||||
data.frame(
|
||||
id = as.integer(input[[paste0("id_", ind)]]),
|
||||
valuta = as.Date(input[[paste0("valuta_", ind)]]),
|
||||
amount = as.numeric(input[[paste0("betrag_", ind)]]),
|
||||
account_id = input[[paste0("account_", ind)]],
|
||||
projekt_id = input[[paste0("projekt_", ind)]],
|
||||
stringsAsFactors = FALSE
|
||||
)
|
||||
})
|
||||
|
||||
postings_data(read_buch_tabelle(conn))
|
||||
selected_trans_id(new_t_id)
|
||||
details_data(read_buch_tabelle(conn, trans_id = new_t_id))
|
||||
# Zu einem großen Dataframe zusammenfügen
|
||||
final_df <- do.call(rbind, all_records)
|
||||
browser()
|
||||
|
||||
neue_zeile <- which(postings_data()$id == p_id1)
|
||||
current_main_idx(neue_zeile)
|
||||
reset_trigger(isolate(reset_trigger()) + 1)
|
||||
scroll_to_row(ns("buchungen_table"), neue_zeile)
|
||||
# Datenbank-Update-Logik
|
||||
tryCatch({
|
||||
# Beispiel: Für jede Zeile ein Update ausführen
|
||||
for(i in 1:nrow(final_df)) {
|
||||
update_buchung_detail(conn, final_df[i, ]) # Deine DB-Funktion
|
||||
}
|
||||
|
||||
showNotification("Daten erfolgreich gespeichert", type = "message")
|
||||
|
||||
# Optional: Haupttabelle aktualisieren, falls sich Summen geändert haben
|
||||
# trigger_refresh(trigger_refresh() + 1)
|
||||
|
||||
}, error = function(e) {
|
||||
showNotification(paste("Fehler beim Speichern:", e$message), type = "error")
|
||||
})
|
||||
})
|
||||
|
||||
# ── Transaktion löschen ────────────────────────────────────────────────────
|
||||
observeEvent(input$del_trans, ignoreInit = TRUE, {
|
||||
req(selected_trans_id())
|
||||
dbxDelete(conn, "postings", where = data.frame(entry_id = selected_trans_id()))
|
||||
dbxDelete(conn, "entries", where = data.frame(id = selected_trans_id()))
|
||||
postings_data(read_buch_tabelle(conn))
|
||||
current_main_idx(NULL)
|
||||
selected_trans_id(NULL)
|
||||
details_data(NULL)
|
||||
updateReactable("buchungen_table", data = gefilterte_daten())
|
||||
})
|
||||
|
||||
# ── Anhänge ────────────────────────────────────────────────────────────────
|
||||
# ── Anhänge ----
|
||||
output$attachments_ui <- renderUI({
|
||||
req(selected_trans_id())
|
||||
req(sel_details())
|
||||
entry_id <-
|
||||
dbxSelect(conn, paste0("SELECT entry_id FROM postings WHERE id=",
|
||||
sel_details()$id[1])) %>% pull
|
||||
att <- dbxSelect(conn, paste0(
|
||||
"SELECT * FROM attachments WHERE entry_id = ", selected_trans_id()
|
||||
"SELECT * FROM attachments WHERE entry_id = ", entry_id
|
||||
))
|
||||
if (nrow(att) == 0) return(p("Keine Anhänge vorhanden."))
|
||||
|
||||
@@ -198,7 +175,7 @@ buchungenServer <- function(id, conn, r_global) {
|
||||
|
||||
observeEvent(input[[paste0("open_att_", att$id[i])]], {
|
||||
showModal(modalDialog(
|
||||
tags$iframe(src = filename, width = "100%", height = "600px",
|
||||
tags$iframe(src = filename, width = "100%", height = "600px",
|
||||
style = "border:none;"),
|
||||
title = att$original_name[i],
|
||||
size = "l",
|
||||
|
||||
+157
-61
@@ -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
|
||||
@@ -51,17 +55,34 @@ umsatzServer <- function(id, conn, r_global) {
|
||||
})
|
||||
})
|
||||
|
||||
# ── Tabelle ────────────────────────────────────────────────────────────────
|
||||
# ── Tabelle ----
|
||||
output$umsatz_table <- renderReactable({
|
||||
# remote_names einmal laden
|
||||
known_names <- dbGetQuery(conn, "SELECT contact_text FROM bank_connections")$contact_text
|
||||
|
||||
reactable(
|
||||
umsatz_data() %>%
|
||||
select(id, valuta, empfaenger_name, empfaenger_konto,
|
||||
betrag, zweck, gebucht, posting_id, entry_id),
|
||||
striped = TRUE,
|
||||
highlight = TRUE,
|
||||
filterable = TRUE, # ← das reicht
|
||||
searchable = T,
|
||||
searchMethod = JS("function(rows, columnIds, filterValue) {
|
||||
var filter = filterValue.toLowerCase().replace(',', '.').replace(/\\s/g, '');
|
||||
return rows.filter(function(row) {
|
||||
return columnIds.some(function(col) {
|
||||
var val = row.values[col];
|
||||
var str = val === null || val === undefined ? '' : String(val);
|
||||
var strNorm = str.toLowerCase().replace(',', '.').replace(/\\s/g, '');
|
||||
return strNorm.includes(filter) ||
|
||||
(typeof val === 'number' &&
|
||||
String(Math.abs(val)).includes(filter));
|
||||
});
|
||||
});
|
||||
}"),
|
||||
filterable = F,
|
||||
pagination = F,
|
||||
selection = "single",
|
||||
selection = "multiple",
|
||||
onClick = "select",
|
||||
defaultSorted = list(valuta = "desc"),
|
||||
columns = list(
|
||||
@@ -69,7 +90,15 @@ umsatzServer <- function(id, conn, r_global) {
|
||||
posting_id = colDef(show = FALSE),
|
||||
entry_id = colDef(show = FALSE),
|
||||
valuta = colDef(name = "Datum", maxWidth = 120),
|
||||
empfaenger_name = colDef(name = "Empfänger", minWidth = 150),
|
||||
empfaenger_name = colDef(
|
||||
name = "Empfänger",
|
||||
minWidth = 150,
|
||||
style = function(value) {
|
||||
if (!is.na(value) && !(value %in% known_names)) {
|
||||
list(color = "red", fontWeight = "bold")
|
||||
}
|
||||
}
|
||||
),
|
||||
empfaenger_konto = colDef(name = "IBAN", minWidth = 150),
|
||||
betrag = colDef(
|
||||
name = "Betrag",
|
||||
@@ -92,7 +121,7 @@ umsatzServer <- function(id, conn, r_global) {
|
||||
)
|
||||
})
|
||||
|
||||
# ── Selektierter Umsatz ────────────────────────────────────────────────────
|
||||
# ── Selektierter Umsatz ----
|
||||
sel <- reactive(getReactableState("umsatz_table", "selected"))
|
||||
|
||||
selected_umsatz <- reactive({
|
||||
@@ -100,21 +129,49 @@ umsatzServer <- function(id, conn, r_global) {
|
||||
umsatz_data()[sel(), ]
|
||||
})
|
||||
|
||||
# ── Buchungs-Panel ─────────────────────────────────────────────────────────
|
||||
# ── 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:",
|
||||
@@ -130,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")
|
||||
)
|
||||
@@ -146,12 +203,12 @@ umsatzServer <- function(id, conn, r_global) {
|
||||
icon = icon("check"))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
# ── Zur Buchung springen ───────────────────────────────────────────────────
|
||||
# ── Zur Buchung springen ----
|
||||
observeEvent(input$goto_buchung, {
|
||||
req(selected_umsatz())
|
||||
r_global$nav_history <- c(r_global$nav_history, list(list(tab = "umsatz")))
|
||||
@@ -159,48 +216,77 @@ umsatzServer <- function(id, conn, r_global) {
|
||||
r_global$active_tab <- "buchungen"
|
||||
})
|
||||
|
||||
# ── Buchen ────────────────────────────────────────────────────────────────
|
||||
# ── 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 ──────────────────────────────────────────────────────────
|
||||
# ── Neuer Kontakt / Bankverbindung ----
|
||||
observeEvent(input$new_contact, {
|
||||
req(selected_umsatz())
|
||||
u <- selected_umsatz()
|
||||
|
||||
contact_choices <- get_contact_choices(conn)
|
||||
showModal(modalDialog(
|
||||
title = "Neuer Kontakt",
|
||||
textInput(ns("new_contact_name"), "Name", value = u$empfaenger_name),
|
||||
selectizeInput(ns("existing_contact_id"),
|
||||
"Vorhandenen Kontakt verknüpfen (optional)",
|
||||
choices = contact_choices,
|
||||
selected = 0,
|
||||
options = list(placeholder = "Kontakt suchen...")
|
||||
),
|
||||
hr(),
|
||||
conditionalPanel(
|
||||
condition = paste0("input['", ns("existing_contact_id"), "'] == '0'"),
|
||||
textInput(ns("new_contact_name"), "Name", value = u$empfaenger_name)
|
||||
),
|
||||
textInput(ns("new_contact_iban"), "IBAN", value = u$empfaenger_konto),
|
||||
footer = tagList(
|
||||
modalButton("Abbrechen"),
|
||||
@@ -209,33 +295,43 @@ umsatzServer <- function(id, conn, r_global) {
|
||||
)
|
||||
))
|
||||
})
|
||||
|
||||
# ** Neuen Kontakt/Bvb speichern
|
||||
observeEvent(input$save_contact, {
|
||||
req(input$new_contact_name)
|
||||
u <- selected_umsatz()
|
||||
|
||||
new_c_id <- max_id(conn, "contacts") + 1L
|
||||
dbxInsert(conn, "contacts", data.frame(
|
||||
id = new_c_id,
|
||||
display_name = input$new_contact_name
|
||||
))
|
||||
|
||||
existing_id <- as.integer(input$existing_contact_id)
|
||||
if (!is.na(existing_id) && existing_id != 0L) {
|
||||
# ── Nur bank_connection anlegen ----
|
||||
target_contact_id <- existing_id
|
||||
|
||||
} else {
|
||||
# ── Neuen Kontakt + bank_connection anlegen ----
|
||||
req(input$new_contact_name)
|
||||
target_contact_id <- max_id(conn, "contacts") + 1L
|
||||
dbxInsert(conn, "contacts", data.frame(
|
||||
id = target_contact_id,
|
||||
display_name = input$new_contact_name
|
||||
))
|
||||
}
|
||||
new_bc_id <- max_id(conn, "bank_connections") + 1L
|
||||
dbxInsert(conn, "bank_connections", data.frame(
|
||||
id = new_bc_id,
|
||||
contact_id = new_c_id,
|
||||
contact_id = target_contact_id,
|
||||
iban = input$new_contact_iban,
|
||||
remote_name = u$empfaenger_name
|
||||
contact_text = u$empfaenger_name
|
||||
))
|
||||
|
||||
removeModal()
|
||||
|
||||
updateSelectizeInput(session, "kontakt",
|
||||
choices = get_contact_choices(conn),
|
||||
selected = new_c_id
|
||||
selected = target_contact_id
|
||||
)
|
||||
|
||||
showNotification("✓ Kontakt angelegt", type = "message")
|
||||
msg <- if (existing_id != 0L) "✓ Bankverbindung verknüpft" else "✓ Kontakt angelegt"
|
||||
showNotification(msg, type = "message")
|
||||
})
|
||||
|
||||
## ** Auswahl aufheben ----
|
||||
observeEvent(input$deselect_all, {
|
||||
updateReactable("umsatz_table", selected = NA, session = session)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
+27
-2
@@ -242,7 +242,7 @@ if (ok) {
|
||||
if (ok) {
|
||||
accounts <- dbxSelect(con_f, "
|
||||
SELECT id, konto, budget_id, bankname, updated_at, created_at,
|
||||
konto_hibiscus, konto_wiso, kontonummer_bank, notizen, b_spenden
|
||||
konto_hibiscus, konto_wiso, kontonummer_bank, notizen, ist_spende
|
||||
FROM Konten
|
||||
") %>%
|
||||
transmute(
|
||||
@@ -253,7 +253,7 @@ if (ok) {
|
||||
budget_id = as.integer(budget_id),
|
||||
wiso_account = konto_wiso,
|
||||
bank_account_no = kontonummer_bank,
|
||||
is_donations = as.integer(b_spenden),
|
||||
is_donations = as.integer(ist_spende),
|
||||
notes = notizen,
|
||||
created_at = created_at,
|
||||
updated_at = updated_at
|
||||
@@ -441,6 +441,30 @@ if(ok){
|
||||
} ## ------------------------------------------------------- 2026-03-19 16:38
|
||||
|
||||
|
||||
## * Daeien übertragen ----
|
||||
if(ok){
|
||||
pfad <- "~/Insync/Projekte/Gemeindefinanzen/gemfin-fm/gemfin04/Dokumente/datei/"
|
||||
zielpfad <- "~/Documents/workspace/gemfin-shiny/www/documents/"
|
||||
vorhanden <- list.files(pfad, pattern = "pdf")
|
||||
eintraege <- att$original_name
|
||||
anz <- length(eintraege)
|
||||
for(ind in 1:length(eintraege)){
|
||||
if( exists(paste0(zielpfad, eintraege[ind])) ){
|
||||
cat(ind, " von ", anz, "\n")
|
||||
file.copy(
|
||||
from = paste0(pfad, vorhanden[ind]),
|
||||
to = "~/Documents/workspace/gemfin-shiny/www/documents/")
|
||||
}
|
||||
}
|
||||
neue_dateien <- list.files("~/Documents/workspace/gemfin-shiny/www/documents/")
|
||||
length(neue_dateien)
|
||||
eintraege <- att$original_name
|
||||
length(eintraege)
|
||||
nv <- att[-which(eintraege %in% neue_dateien),]
|
||||
error_f <- fehler_add("Alle Dateien übertragen", nrow(nv) == 0, error_f)
|
||||
} ## ------------------------------------------------------- 2026-04-28 18:40
|
||||
|
||||
|
||||
# Transfer Bankverbindungen ----
|
||||
if (ok) {
|
||||
bank_connections <- dbxSelect(con_f, "
|
||||
@@ -460,6 +484,7 @@ if (ok) {
|
||||
updated_at = as.character(updated_at)
|
||||
)
|
||||
|
||||
sum(duplicated(bank_connections$id))
|
||||
dbWriteTable(con_s, "bank_connections", bank_connections, append = TRUE)
|
||||
error_f <- fehler_add(
|
||||
paste(nrow(bank_connections), "Bankverbindungen übertragen"), TRUE, error_f
|
||||
|
||||
Binary file not shown.
@@ -54,4 +54,5 @@ server <- function(input, output, session) {
|
||||
accountsServer("accounts_tab", conn, r_global)
|
||||
buchungenServer("buchungen_tab", conn, r_global)
|
||||
umsatzServer("umsatz_tab", conn, r_global)
|
||||
contactsServer("contacts_tab", conn, r_global)
|
||||
}
|
||||
@@ -49,11 +49,15 @@ dashboardPage(
|
||||
sidebarMenu(id = "tabs",
|
||||
menuItem("Buchungen", tabName = "buchungen", icon = icon("list")),
|
||||
menuItem("Umsätze", tabName = "umsatz", icon = icon("bank")),
|
||||
menuItem("Konten", tabName = "konten", icon = icon("building-columns"))
|
||||
menuItem("Konten", tabName = "konten", icon = icon("building-columns")),
|
||||
menuItem("Adressen", tabName = "contacts", icon = icon("people-group"))
|
||||
)
|
||||
),
|
||||
|
||||
dashboardBody(
|
||||
tags$head(
|
||||
tags$link(rel = "stylesheet", type = "text/css", href = "main.css")
|
||||
),
|
||||
tabItems(
|
||||
tabItem(tabName = "buchungen",
|
||||
buchungenUI("buchungen_tab")
|
||||
@@ -63,6 +67,9 @@ dashboardPage(
|
||||
),
|
||||
tabItem(tabName = "konten",
|
||||
accountsUI("accounts_tab")
|
||||
),
|
||||
tabItem(tabName = "contacts",
|
||||
contactsUI("contacts_tab")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user