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
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()
|
|
}
|
|
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
})
|
|
|