feat: функционал импорта базы данных из xlsx таблицы

This commit is contained in:
2026-04-08 18:24:55 +03:00
parent 3a70299648
commit 04e8242b56
2 changed files with 149 additions and 20 deletions

114
app.R
View File

@@ -166,6 +166,7 @@ ui <- page_sidebar(
actionButton("load_data_button", "Загрузить данные", icon("pencil", lib = "font-awesome")), actionButton("load_data_button", "Загрузить данные", icon("pencil", lib = "font-awesome")),
downloadButton("downloadData", "Экспорт в .xlsx"), downloadButton("downloadData", "Экспорт в .xlsx"),
downloadButton("downloadDocx", "get .docx (test only)"), downloadButton("downloadDocx", "get .docx (test only)"),
actionButton("button_upload_data_from_xlsx", "импорт!"),
position = "left", position = "left",
open = list(mobile = "always") open = list(mobile = "always")
), ),
@@ -797,6 +798,7 @@ server <- function(input, output, session) {
## загрузка данных по главному ключу ------------------ ## загрузка данных по главному ключу ------------------
observeEvent(input$load_data, { observeEvent(input$load_data, {
req(input$load_data_key_selector)
con <- db$make_db_connection("load_data") con <- db$make_db_connection("load_data")
on.exit(db$close_db_connection(con, "load_data"), add = TRUE) on.exit(db$close_db_connection(con, "load_data"), add = TRUE)
@@ -963,6 +965,118 @@ server <- function(input, output, session) {
} }
) )
## upload xlsx to df ----------------------
observeEvent(input$button_upload_data_from_xlsx, {
showModal(modalDialog(
title = "Добавить пациентов к текущему списку...",
fileInput(
"upload_xlsx",
NULL,
buttonLabel = "Выбрать файлы...",
placeholder = "No file selected",
multiple = TRUE,
accept = ".xlsx",
width = 450
),
checkboxInput("upload_data_from_xlsx_owerwrite_all_data", "перезаписать все данные", width = 450),
footer = tagList(
actionButton("button_upload_data_from_xlsx_confirm", "Добавить")
),
easyClose = TRUE
))
})
observeEvent(input$button_upload_data_from_xlsx_confirm, {
req(input$upload_xlsx
)
con <- db$make_db_connection("button_upload_data_from_xlsx_confirm")
on.exit(db$close_db_connection(con, "button_upload_data_from_xlsx_confirm"), add = TRUE)
file <- input$upload_xlsx$datapath
# print(file)
wb <- openxlsx2::wb_load(file)
# проверка на наличие всех листов в файле
if (!all(names(SCHEMES_LIST) %in% openxlsx2::wb_get_sheet_names(wb))) {
cli::cli_alert_warning("данные в файле '{file} не соответствуют схеме'")
return()
}
# проверка схемы --------------
for (table_name in names(SCHEMES_LIST)) {
df <- openxlsx2::read_xlsx(wb, table_name)
scheme <- SCHEMES_LIST[[table_name]] |>
filter(!form_type %in% c("description", "nested_forms"))
# столбцы в таблицы и схема
df_to_schema_compare <- setdiff(colnames(df), unique(scheme$form_id))
if (length(schema_to_df_compare) > 0 ) {
cli::cli_warn(c("в схеме для '{table_name}' нет следующих столбцов:", paste("- ", df_to_schema_compare)))
}
# схема и столбцы в таблице
schema_to_df_compare <- setdiff(unique(scheme$form_id), colnames(df))
if (length(schema_to_df_compare) > 0 ) {
message <- glue::glue("столбцы в таблице '{table_name}' не соответсвуют схеме")
cli::cli_warn(c(message, paste("- ", schema_to_df_compare)))
showNotification(message, type = "error")
return()
}
}
# обновление данных
for (table_name in names(SCHEMES_LIST)) {
df <- openxlsx2::read_xlsx(wb, table_name)
scheme <- SCHEMES_LIST[[table_name]] |>
filter(!form_type %in% c("description", "nested_forms"))
date_columns <- subset(scheme, form_type == "date", form_id, drop = TRUE)
df <- df |>
dplyr::mutate(
# даты - к единому формату
dplyr::across(tidyselect::all_of({{date_columns}}), \(x) purrr::map_chr(x, db$excel_to_db_dates_converter)),
) |>
select(all_of(unique(c("main_key", scheme$form_id))))
df_original <- DBI::dbReadTable(con, table_name) |>
as_tibble()
if (input$upload_data_from_xlsx_owerwrite_all_data == TRUE) cli::cli_abort("not implemented yet")
walk(
.x = unique(df$main_key),
.f = \(main_key) {
if (main_key %in% unique(df_original$main_key)) {
DBI::dbExecute(con, glue::glue("DELETE FROM {table_name} WHERE main_key = '{main_key}'"))
}
}
)
DBI::dbWriteTable(
con,
name = table_name,
value = df,
append = TRUE
)
message <- glue::glue("Данные таблицы '{table_name}' успешно обновлены")
showNotification(
message,
type = "message"
)
cli::cli_alert_success(message)
}
removeModal()
})
## cancel ========================== ## cancel ==========================
observeEvent(input$cancel_button, { observeEvent(input$cancel_button, {
removeModal() removeModal()

View File

@@ -165,25 +165,6 @@ write_df_to_db <- function(df, table_name, scheme, main_key, nested_key, con) {
date_columns <- subset(scheme, form_type == "date", form_id, drop = TRUE) date_columns <- subset(scheme, form_type == "date", form_id, drop = TRUE)
number_columns <- subset(scheme, form_type == "number", form_id, drop = TRUE) number_columns <- subset(scheme, form_type == "number", form_id, drop = TRUE)
excel_to_db_dates_converter <- function(date) {
parse_date1 <- tryCatch(
as.Date(date, tryFormats = c("%Y-%m-%d")),
error = function(e) NULL
)
parse_date2 <- suppressWarnings(as.Date(as.numeric(date), origin = "1899-12-30"))
date <- if (!is.null(parse_date1)) {
parse_date1
} else if (!is.na(parse_date2)) {
parse_date2
} else {
date
}
date <- as.character(format(date, "%Y-%m-%d"))
}
df <- df |> df <- df |>
dplyr::mutate( dplyr::mutate(
# даты - к единому формату # даты - к единому формату
@@ -261,3 +242,37 @@ get_nested_keys_from_table <- function(table_name, main_key, con) {
dplyr::pull() dplyr::pull()
} }
### HELPERS ---------
#' @export
excel_to_db_dates_converter <- function(date) {
if(is.na(date)) return(NA)
# cli::cli_inform("date: {date} | nchar: {nchar(date)} | typeof: {typeof(date)}")
# если текст, количество символов 7, и маска соответствует 'MM.YYYY'
if (typeof(date) == "character" & nchar(date) == 4 & grepl("((?:19|20)\\d\\d)", date)) {
date <- sprintf("%s-01-01", date)
} else if (typeof(date) == "character" & nchar(date) == 7 & grepl("(0?[1-9]|1[012])\\.((?:19|20)\\d\\d)", date)) {
# если текст, количество символов 7, и маска соответствует 'MM.YYYY'
date <- sprintf("01.%s", date)
}
parse_date1 <- tryCatch(
as.Date(date, tryFormats = c("%d.%m.%Y", "%Y-%m-%d")),
error = function(e) NULL
)
parse_date2 <- suppressWarnings(as.Date(as.numeric(date), origin = "1899-12-30"))
date <- if (!is.null(parse_date1)) {
parse_date1
} else if (!is.na(parse_date2)) {
parse_date2
} else {
date
}
date <- as.character(format(date, "%Y-%m-%d"))
date
}