You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

568 lines
15 KiB

# Server logic
# Budget variation tracking of Aramco
# ::::::Asitav Sen::::::
# ::LaNubia Consulting::
library(shiny)
# Define server logic
shinyServer(function(input, output, session) {
res_auth <- secure_server(
check_credentials = check_credentials("cred.sqlite",
passphrase = "kJuyhG657Hj&^%gshj*762hjsknh&662"),
keep_token=TRUE
)
output$menu <- renderMenu({
if (res_auth$admin == FALSE) {
other.menu
} else
admin.menu
})
# Connect to DB
connec <- dbConnect(
RPostgres::Postgres(),
dbname = dsn_database,
host = dsn_hostname,
port = dsn_port,
user = dsn_uid,
password = dsn_pwd
)
# On stop disconnect from DB
onStop(function() {
dbDisconnect(connec)
})
# Getting the data from DB
dat <- reactive({
dbGetQuery(connec,
'SELECT * FROM calculated')
#get.db.data('SELECT * FROM calculated')
})
# Getting deviation limits from DB
lims <- reactive({
dbGetQuery(connec,
'SELECT * FROM limits')
#get.db.data('SELECT * FROM limits')
})
# Getting the explanations from DB
exp <- reactive({
dbGetQuery(connec,
'SELECT * FROM explanations')
#get.db.data('SELECT * FROM explanations')
})
# Getting approval Table from DB
approvals <- reactive({
dbGetQuery(connec,
'SELECT * FROM approvals')
#get.db.data('SELECT * FROM approvals')
})
# Getting email ids
emailids <- reactive({
dbGetQuery(connec,
'SELECT * FROM emails')
#get.db.data('SELECT * FROM emails')
})
dat.need.exp <- reactive({
req(dat())
dat() |>
inner_join(lims(), by = c("GL.account")) |>
mutate(act.req = ifelse(devia.per > Limit &
devia > input$limittoignore, T, F)) |>
filter(act.req) |>
left_join(
exp(),
by = c(
"month" = "month",
"Cost.center" = "Cost.center",
"GL.account" = "GL.account"
)
) |>
filter(is.na(explanation) | explanation == "")
})
# Update the gl account selector
observeEvent(input$costcenter, {
req(dat())
choi <- dat() |>
filter(Cost.center %in% input$costcenter) |>
select(GL.account) |> distinct() |> pull()
updatePickerInput(
session = session,
inputId = "glaccount",
choices = choi,
selected = choi
)
})
observeEvent(c(input$costcenter, input$glaccount), {
# Creating data that will be filtered using the GL and cost center selection
dat.filtered <- dat() |>
filter(Cost.center %in% input$costcenter) |>
filter(GL.account %in% input$glaccount) |>
mon.dev() |>
filter(month == max(month))
last.var.per <- dat.filtered |>
pull(deviation.percent) #Last variation percent
last.var.abs <- dat.filtered |>
pull(devia) #Last variation absolute val
last.exp.act <- dat.filtered |>
pull(Actual) #Last expense
valueServer(
"variation",
ttl = "Latest Variation %",
n = last.var.per,
icn = "percent",
clr = "red",
symbl = "%"
)
valueServer(
"variabs",
ttl = "Last Variation Amount",
n = last.var.abs,
icn = "dollar-sign",
clr = "green",
symbl = "$"
)
valueServer(
"actexpe",
ttl = "Last Expense",
n = last.exp.act,
icn = "coins",
clr = "blue",
symbl = "$"
)
})
# Data selected after aplying the filters of cost center and gl account
dat.selected <- reactive({
req(dat())
req(input$costcenter)
req(input$glaccount)
dat() |>
filter(Cost.center %in% input$costcenter) |>
filter(GL.account %in% input$glaccount)
})
# Monthly aggregate
dat.monthly.per <- reactive({
req(dat.selected())
dat.selected() |>
mutate(month = ym(month)) |>
group_by(month) |>
summarise(Plan = sum(Plan), Actual = sum(Actual)) |>
mutate(devia = Actual - Plan) |>
mutate(deviation.percent = round(devia * 100 / Plan, 2))
})
# Monthly percentage deviation
output$monthlypervar <- renderEcharts4r({
req(dat.monthly.per())
e_chart(dat.monthly.per(), x = month) |>
e_line(serie = deviation.percent,
smooth = T,
color = "cyan", opacity=0.8) |>
e_area(serie = deviation.percent,
smooth = T,
color = "gray", opacity=0.6) |>
e_axis_labels(x = "month", y = "Deviation") |>
e_format_y_axis(suffix = " %") |>
e_tooltip() |>
e_legend(right = 100) |>
e_datazoom(x_index = c(0, 1)) |>
e_image_g(
right = 50,
top = 20,
z = -999,
style = list(
image = "logo.png",
width = 75,
height = 75,
opacity = .6
)
) |>
e_toolbox_feature(feature = c("saveAsImage", "dataView")) |>
e_theme("roma")
})
# Monthly Absolute Deviation
dat.monthly.abs <- reactive({
dat.selected() |>
filter(month == input$mont) |>
mutate(cost_gl = paste0(Cost.center, "_", GL.account)) |>
group_by(cost_gl) |>
summarise(Plan = sum(Plan), Actual = sum(Actual)) |>
mutate(devia = Actual - Plan) |>
mutate(deviation.percent = round(devia * 100 / Plan, 2)) |>
arrange(desc(deviation.percent))
})
# Monthly absolute by cost center + gl account
output$monthlyabs <- renderEcharts4r({
req(dat.monthly.abs())
dat.monthly.abs() |>
e_charts(cost_gl) |>
e_bar(Plan, name = "Plan", color = "gray", opacity=0.6) |>
e_step(Actual, name = "Actual", color = "red") |>
e_axis_labels(x = "GL+Cost Center", y = "Deviation") |>
#e_title("Plan Vs. Actual") |>
e_tooltip() |>
e_legend(right = 100) |>
e_datazoom(x_index = 0, type = "slider") |>
e_datazoom(y_index = 0, type = "slider") |>
e_image_g(
right = 50,
top = 20,
z = -999,
style = list(
image = "logo.png",
width = 75,
height = 75,
opacity = .6
)
) |>
e_toolbox_feature(feature = c("saveAsImage", "dataView")) |>
e_theme("roma")
})
output$monthlyabsvar <- renderEcharts4r({
req(dat.monthly.per())
e_chart(dat.monthly.per(), x = month) |>
e_bar(serie = devia) |>
e_axis_labels(x = "month", y = "Deviation") |>
e_format_y_axis(suffix = "€") |>
#e_title("Deviation Percentage by month") |>
e_tooltip() |>
e_legend(right = 100) |>
e_datazoom(x_index = c(0, 1)) |>
e_image_g(
right = 50,
top = 20,
z = -999,
style = list(
image = "logo.png",
width = 75,
height = 75,
opacity = .6
)
) |>
e_visual_map(type = "piecewise",
pieces = list(list(gt = 0,
color = "red", opacity=0.5),
list(lte = 0,
color = "green", opacity=0.5))) |>
e_toolbox_feature(feature = c("saveAsImage", "dataView")) |>
e_theme("roma")
})
# Table of deviations without explanation
datatableServer("devnoexp", dat.need.exp)
# Notify
observeEvent(input$notify, {
req(dat.need.exp())
emaildf<-dat.need.exp() |>
inner_join(emailids(), by=c("Cost.center"="Cost.center","GL.account"="GL.account"))
print(emaildf)
if(is.null(emaildf) | nrow(emaildf)==0){
showModal(modalDialog("No Data"))
} else {
emails<-
emaildf |>
select(email) |> pull() |> unique()
for(i in 1:length(emails)){
tbl <- emaildf |>
filter(email==emails[i]) |> kbl()
date_time <- add_readable_time()
email <-
compose_email(body = md(c(
glue::glue(
"Hello,
Explanation required for expense deviation. Please visit xyz. Details are as follows.
"
),
tbl
)),
footer = md(glue::glue("Email sent on {date_time}.")))
email |>
smtp_send(
to = emails[i],
from = "asitav.sen@lanubia.com",
subject = "Testing",
credentials = creds_file("email_creds")
)
}
showModal(modalDialog(title = "Done"))
}
})
# Approvals
output$expnoapp <- renderExcel({
req(approvals())
appro <- approvals()
row.names(appro) <- NULL
appro <- appro[appro$approved == FALSE, ]
columns = data.frame(
title = colnames(appro),
type = c('numeric', 'text', 'text', 'text', 'checkbox')
)
excelTable(data = appro, columns = columns, autoFill = TRUE)
})
# Update approvals
observeEvent(input$approvalsubmit, {
approved.now <- excel_to_R(input$expnoapp)
if (is.null(input$expnoapp) || nrow(approved.now) == 0) {
showModal(modalDialog(title = "Nothing to Upload"))
} else{
showModal(modalDialog(title = "Upload in Database?",
actionButton("finalapprovalsubmit", "Yes")))
}
})
observeEvent(input$finalapprovalsubmit, {
appro <- approvals()
appro <- appro[appro$approved == TRUE, ]
approved.now <- excel_to_R(input$expnoapp)
appro <- rbind(appro, approved.now)
if (nrow(approved.now) > 0) {
if (dbWriteTable(connec, "approvals", appro, overwrite = TRUE)) {
removeModal()
session$reload()
}
}
})
# New Data
fresh.dat <- reactive({
req(input$inpfile)
file <- input$inpfile
ext <- tools::file_ext(file$datapath)
req(file)
validate(need(ext == "csv", "Please upload a csv file"))
new.dat <- read.csv(file$datapath, header = T)
new.dat |>
mutate(devia = Actual - Plan) |>
mutate(devia.per = round(devia / Plan, 2))
}, label = "fresh")
# Show uploaded data
datatableServer("freshdat", fresh.dat)
# Update DB
observeEvent(input$datsubmit, {
req(nrow(fresh.dat()) > 0)
fresh.dt <- fresh.dat()
if (is.null(fresh.dt) || nrow(fresh.dt) == 0) {
showModal(modalDialog(title = "Nothing to Upload"))
} else{
showModal(modalDialog(title = "Upload in Database?",
actionButton("finaldatsubmit", "Yes")))
}
})
observeEvent(input$finaldatsubmit, {
fresh.dt <- fresh.dat()
if (nrow(fresh.dt) > 0) {
if (dbWriteTable(connec, "calculated", fresh.dt, append = TRUE)) {
unique(fresh.dt$month) |> saveRDS("./data/months.RDS")
removeModal()
session$reload()
}
}
})
# Email id edit and show
output$emailtable <- renderExcel({
req(emailids())
email.table <- emailids()
columns = data.frame(title = colnames(email.table),
type = c('text', 'text', 'text'))
excelTable(data = email.table, columns = columns, autoFill = TRUE)
})
# Email id table update
observeEvent(input$emailsubmit, {
email.table <- excel_to_R(input$emailtable)
if (is.null(input$emailtable) || nrow(email.table) == 0) {
showModal(modalDialog(title = "Nothing to Upload"))
} else{
showModal(modalDialog(title = "Upload in Database?",
actionButton("finalemailsubmit", "Yes")))
}
})
observeEvent(input$finalemailsubmit, {
email.table <- excel_to_R(input$emailtable)
if (nrow(email.table) > 0) {
if (dbWriteTable(connec, "emails", email.table, overwrite = TRUE)) {
removeModal()
session$reload()
}
}
})
# Limits (deviation rules) id edit and show
output$limitable <- renderExcel({
req(lims())
limit.table <- lims()
columns = data.frame(title = colnames(limit.table),
type = c('text', 'numeric'))
excelTable(data = limit.table, columns = columns, autoFill = TRUE)
})
# Email id table update
observeEvent(input$limitsubmit, {
limit.table <- excel_to_R(input$limitable)
if (is.null(input$limitable) || nrow(limit.table) == 0) {
showModal(modalDialog(title = "Nothing to Upload"))
} else{
showModal(modalDialog(title = "Upload in Database?",
actionButton("finallimitsubmit", "Yes")))
}
})
observeEvent(input$finallimitsubmit, {
limit.table <- excel_to_R(input$limitable)
if (nrow(limit.table) > 0) {
if (dbWriteTable(connec, "limits", limit.table, overwrite = TRUE)) {
removeModal()
session$reload()
}
}
})
# User specific explanation filter
user.accounts <- reactive({
req(dat())
req(emailids())
req(exp())
req(res_auth$email)
dat() |>
inner_join(lims(), by = c("GL.account")) |>
mutate(act.req = ifelse(devia.per > Limit &
devia > input$limittoignore, T, F)) |> #change 500 to input
filter(act.req) |>
left_join(emailids(),
by = c("Cost.center" = "Cost.center", "GL.account" = "GL.account")) |>
left_join(
exp(),
by = c(
"month" = "month",
"Cost.center" = "Cost.center",
"GL.account" = "GL.account"
)
) |>
filter(is.na(explanation) | explanation == "") |>
filter(email == res_auth$email) |>
select(-c(9, 10))
})
output$explanationtofill <- renderExcel({
req(user.accounts())
explanation.table <- user.accounts()
columns = data.frame(
title = colnames(explanation.table),
type = c(
'numeric',
'text',
'text',
'numeric',
'numeric',
'numeric',
'numeric',
'numeric',
'text'
)
)
excelTable(data = explanation.table, columns = columns, autoFill = TRUE)
})
# Explanations table update
observeEvent(input$explanationsubmit, {
explanation.table <- excel_to_R(input$explanationtofill)
if (is.null(input$explanationtofill) ||
nrow(explanation.table) == 0) {
showModal(modalDialog(title = "Nothing to Upload"))
} else{
showModal(modalDialog(title = "Upload in Database?",
actionButton("finalexpsubmit", "Yes")))
}
})
observeEvent(input$finalexpsubmit, {
explanation.table <- excel_to_R(input$explanationtofill)
explanation.table <- explanation.table |>
select(c(month, Cost.center, GL.account, explanation))
if (nrow(explanation.table) > 0) {
if (dbWriteTable(connec, "explanations", explanation.table, append = TRUE)) {
appr <- explanation.table |> mutate(approved = F)
if (dbWriteTable(connec, "approvals", appr, append = TRUE)) {
removeModal()
session$reload()
}
}
}
})
})