From f93702dd8779e9bedfecb47c473cd96a8152fe1f Mon Sep 17 00:00:00 2001 From: Asitav Sen Date: Wed, 13 Jul 2022 06:19:07 +0200 Subject: [PATCH] Ver 0.1 --- .gitignore | 4 + Pre_runner.Rmd | 212 ++++++++++++++++ bUdgEtRack.Rproj | 13 + cred.sqlite | Bin 0 -> 36864 bytes data/Jul22.csv | 23 ++ data/Jun22.csv | 23 ++ data/apr22.csv | 23 ++ data/costcenters.RDS | Bin 0 -> 147 bytes data/dev.limits.csv | 7 + data/email_creds | 1 + data/glaccounts.RDS | Bin 0 -> 131 bytes data/initial.csv | 67 +++++ data/may22.csv | 23 ++ data/may22.xlsx | Bin 0 -> 9482 bytes data/months.RDS | Bin 0 -> 61 bytes email_creds | 1 + helper_server.R | 87 +++++++ helper_ui.R | 169 +++++++++++++ mod_datatable.R | 29 +++ mod_elinearea.R | 32 +++ mod_step.R | 27 ++ mod_valuebox.R | 24 ++ server.R | 568 +++++++++++++++++++++++++++++++++++++++++++ ui.R | 125 ++++++++++ www/loader.gif | Bin 0 -> 69769 bytes www/logo.png | Bin 0 -> 11645 bytes 26 files changed, 1458 insertions(+) create mode 100644 .gitignore create mode 100644 Pre_runner.Rmd create mode 100644 bUdgEtRack.Rproj create mode 100644 cred.sqlite create mode 100644 data/Jul22.csv create mode 100644 data/Jun22.csv create mode 100644 data/apr22.csv create mode 100644 data/costcenters.RDS create mode 100644 data/dev.limits.csv create mode 100644 data/email_creds create mode 100644 data/glaccounts.RDS create mode 100644 data/initial.csv create mode 100644 data/may22.csv create mode 100644 data/may22.xlsx create mode 100644 data/months.RDS create mode 100644 email_creds create mode 100644 helper_server.R create mode 100644 helper_ui.R create mode 100644 mod_datatable.R create mode 100644 mod_elinearea.R create mode 100644 mod_step.R create mode 100644 mod_valuebox.R create mode 100644 server.R create mode 100644 ui.R create mode 100644 www/loader.gif create mode 100644 www/logo.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b6a065 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.Rproj.user +.Rhistory +.RData +.Ruserdata diff --git a/Pre_runner.Rmd b/Pre_runner.Rmd new file mode 100644 index 0000000..49bc3a0 --- /dev/null +++ b/Pre_runner.Rmd @@ -0,0 +1,212 @@ +--- +title: "Pre" +author: "Scary Scarecrow" +date: '2022-06-27' +output: html_document +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE) +library(dplyr) +library(echarts4r) +library(lubridate) +library(shinymanager) +``` + +## Credentials + +```{r} +credentials <- data.frame( + user = c("shiny", "asitav", "rigo", "aldo"), + password = c("lanubia@2021", "lanubia@2021","lanubia@2021","lanubia@2021"), + admin = c(FALSE, TRUE, FALSE, FALSE), + email = c("hello@asitavsen.com","asitav.sen@lanubia.com","rigo.selassa@lanubia.com","aldo.silvano@lanubia.com"), + stringsAsFactors = FALSE +) + +create_db( + credentials_data = credentials, + sqlite_path = "./cred.sqlite", # will be created + passphrase = "kJuyhG657Hj&^%gshj*762hjsknh&662" +) +``` + + +## DB + +```{r} +connec <- dbConnect( + RPostgres::Postgres(), + dbname = dsn_database, + host = dsn_hostname, + port = dsn_port, + user = dsn_uid, + password = dsn_pwd +) +``` + + + +```{r} +#dat<-read.csv("./data/initial.csv") +#lims<-read.csv("./data/dev.limits.csv") +dat.1<- + dat |> + mutate(devia=Actual-Plan) |> + mutate(devia.per=round(devia/Plan,2)) +#dbCreateTable(connec, "calculated", dat.1) +dbWriteTable(connec, "calculated", dat.1, append=TRUE) +dbWriteTable(connec, "limits", lims, append=TRUE) + +#dbRemoveTable(connec,"calculated") +dat<-dbGetQuery( + connec, + 'SELECT * FROM calculated' +) + +lims<-dbGetQuery( + connec, + 'SELECT * FROM limits' +) + +#dbReadTable(connec, "calculated") + +exp<- + dat |> + inner_join(lims, by=c("GL.account")) |> + mutate(act.req=ifelse(devia.per>Limit & devia > 500,T,F)) |> + filter(act.req) |> + select(1:3) |> + mutate(explanation=c("Cyberattack","Overwork","Interview","Maintenance","Quarterly Report","New Legislation")) + +dbWriteTable(connec, "explanations", exp, append=TRUE) +exp<-dbGetQuery( + connec, + 'SELECT * FROM explanations' +) + +approvals<- exp |> mutate(approved=F) + +dbWriteTable(connec, "approvals", approvals, overwrite=T) + +approvals<-dbGetQuery( + connec, + 'SELECT * FROM approvals' +) + + + dat |> + inner_join(lims, by=c("GL.account")) |> + mutate(act.req=ifelse(devia.per>Limit & devia > 500,T,F)) |> + filter(act.req) |> + left_join(exp, by=c("month"="month","Cost.center"="Cost.center","GL.account"="GL.account")) + + + emailsids<-dat |> + select(Cost.center, GL.account) |> + distinct() |> + mutate(email=c("asitav.sen@lanubia.com")) +dbWriteTable(connec, "emails", emailsids, overwrite=T) +emailsids<-dbGetQuery( + connec, + 'SELECT * FROM emails' +) + +sel.emails<- emailsids |> + filter(email=="asitav.sen@lanubia.com") + + dat |> + inner_join(lims, by=c("GL.account")) |> + mutate(act.req=ifelse(devia.per>Limit & devia > 500,T,F)) |> + filter(act.req) |> + left_join(sel.emails, 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=="asitav.sen@lanubia.com") |> + select(-c(9,10)) + + exp +``` + + + +```{r} + +dat.2<- + dat |> + 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)) + + e_chart(dat.2, x=month) |> + e_line(serie = deviation.percent, smooth=T, color="cyan") |> + e_area(serie = deviation.percent, smooth=T, color="gray") |> + e_axis_labels(x = "month", y="Deviation") |> + e_format_y_axis(suffix = " %") |> + e_title("Deviation", "Selected Cost Centers") |> + e_tooltip() |> + e_legend(right = 100) |> + e_datazoom(x_index = c(0, 1)) |> + e_toolbox_feature(feature = c("saveAsImage","dataView")) |> + e_theme("chalk") + + history<-dat.2 |> select(month,deviation.percent) |> rename(ds=month, y=deviation.percent) + model <- prophet::prophet(history) + future <- prophet::make_future_dataframe(model, periods = 2) + forecast <- predict(model, future) + +dat |> + filter(month==max(month)) |> + 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)) |> + e_charts(cost_gl) |> + e_bar(Plan, name = "Plan", color="gray") |> + e_step(Actual, name = "Actual", color="red") |> + e_axis_labels(x = "GL+Cost Center", y="Deviation") |> + e_title("Selected Cost Centers") |> + e_tooltip() |> + e_legend(right = 100) |> + e_datazoom(x_index = 0, type = "slider") |> + e_datazoom(y_index = 0, type = "slider") |> + e_toolbox_feature(feature = c("saveAsImage","dataView")) |> + e_theme("chalk") +``` +```{r} +unique(dat$Cost.center) |> saveRDS("./data/costcenters.RDS") +unique(dat$GL.account) |> saveRDS("./data/glaccounts.RDS") +unique(dat$month) |> saveRDS("./data/months.RDS") +lims +``` + + + + + +```{r} +dat.dev<-dat |> + filter(devia>0) |> + select(2,3,6) + +e_charts(dat.dev) |> + e_pie(devia) + +blastula::create_smtp_creds_file( + file = "email_creds", + user = "apikey", + host = "smtp.sendgrid.net", + port = 465, + use_ssl = TRUE +) + +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 +``` + + + + diff --git a/bUdgEtRack.Rproj b/bUdgEtRack.Rproj new file mode 100644 index 0000000..8e3c2eb --- /dev/null +++ b/bUdgEtRack.Rproj @@ -0,0 +1,13 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX diff --git a/cred.sqlite b/cred.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..3bfbfd44bef6d560c9ea2d11a498532e3b0d900f GIT binary patch literal 36864 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 BLLLAB literal 0 HcmV?d00001 diff --git a/data/dev.limits.csv b/data/dev.limits.csv new file mode 100644 index 0000000..dd7182d --- /dev/null +++ b/data/dev.limits.csv @@ -0,0 +1,7 @@ +GL account,Limit +Office lease,0.15 +Events,0.10 +Consultancy,0.10 +Business travel,0.10 +Salaries,0.10 +Training costs,0.15 \ No newline at end of file diff --git a/data/email_creds b/data/email_creds new file mode 100644 index 0000000..4f69aff --- /dev/null +++ b/data/email_creds @@ -0,0 +1 @@ +{"type":"list","attributes":{"names":{"type":"character","attributes":{},"value":["version","host","port","use_ssl","user","password"]}},"value":[{"type":"integer","attributes":{},"value":[1]},{"type":"character","attributes":{},"value":["smtp.sendgrid.net"]},{"type":"double","attributes":{},"value":[465]},{"type":"logical","attributes":{},"value":[true]},{"type":"character","attributes":{},"value":["apikey"]},{"type":"character","attributes":{},"value":["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"]}]} diff --git a/data/glaccounts.RDS b/data/glaccounts.RDS new file mode 100644 index 0000000000000000000000000000000000000000..eda29654b889002b6ff1d125c0d6d3bfb7e0e249 GIT binary patch literal 131 zcmb2|=3oE==I#ec2?+^l35h9532CfGk`d0%cS>{{co-GZk`odiuw0Tk*lF46I&t0WQm&CawdkHOV_tg1lkP%N4_-R literal 0 HcmV?d00001 diff --git a/data/initial.csv b/data/initial.csv new file mode 100644 index 0000000..d896442 --- /dev/null +++ b/data/initial.csv @@ -0,0 +1,67 @@ +month,Cost center,GL account,Plan,Actual +202201,IT,Salaries,30000,29000 +202201,IT,Business travel,5000,6150 +202201,IT,Consultancy,10000,12000 +202201,IT,Training costs,5000,4800 +202201,HR local support,Salaries,15000,15500 +202201,HR local support,Events,5000,4900 +202201,HR local support,Training costs,2000,1500 +202201,Finance,Salaries,13000,13000 +202201,Finance,Consultancy,6000,6500 +202201,Finance,Training costs,4000,4500 +202201,Purchasing,Salaries,40000,41500 +202201,Purchasing,Consultancy,2000,2000 +202201,Purchasing,Training costs,1000,1000 +202201,HR services,Salaries,35000,36000 +202201,HR services,Business travel,10000,14000 +202201,HR services,Training costs,2000,1800 +202201,Public Relations,Salaries,10000,8000 +202201,Public Relations,Events,18000,19000 +202201,Public Relations,Business travel,2000,1950 +202201,Public Relations,Training costs,600,500 +202201,Facility management,Office lease,50000,48000 +202201,Facility management,Salaries,5000,4500 +202202,IT,Salaries,30000,29000 +202202,IT,Business travel,5000,5000 +202202,IT,Consultancy,10000,9000 +202202,IT,Training costs,5000,5000 +202202,HR local support,Salaries,15000,15500 +202202,HR local support,Events,0,0 +202202,HR local support,Training costs,2000,2200 +202202,Finance,Salaries,13000,13000 +202202,Finance,Consultancy,6000,5000 +202202,Finance,Training costs,0,0 +202202,Purchasing,Salaries,40000,41500 +202202,Purchasing,Consultancy,2000,2000 +202202,Purchasing,Training costs,1000,0 +202202,HR services,Salaries,35000,36000 +202202,HR services,Business travel,10000,7000 +202202,HR services,Training costs,2000,2000 +202202,Public Relations,Salaries,10000,8000 +202202,Public Relations,Events,18000,10000 +202202,Public Relations,Business travel,2000,2200 +202202,Public Relations,Training costs,600,600 +202202,Facility management,Office lease,50000,48000 +202202,Facility management,Salaries,5000,4500 +202203,IT,Salaries,30000,30000 +202203,IT,Business travel,5000,6000 +202203,IT,Consultancy,10000,10500 +202203,IT,Training costs,5000,5000 +202203,HR local support,Salaries,15000,15000 +202203,HR local support,Events,10000,5000 +202203,HR local support,Training costs,2000,1000 +202203,Finance,Salaries,13000,13000 +202203,Finance,Consultancy,6000,7000 +202203,Finance,Training costs,500,500 +202203,Purchasing,Salaries,40000,40000 +202203,Purchasing,Consultancy,2000,2000 +202203,Purchasing,Training costs,1000,1000 +202203,HR services,Salaries,35000,35000 +202203,HR services,Business travel,10000,10000 +202203,HR services,Training costs,2000,3000 +202203,Public Relations,Salaries,10000,10600 +202203,Public Relations,Events,18000,18000 +202203,Public Relations,Business travel,2000,1000 +202203,Public Relations,Training costs,600,1000 +202203,Facility management,Office lease,50000,50000 +202203,Facility management,Salaries,5000,5000 \ No newline at end of file diff --git a/data/may22.csv b/data/may22.csv new file mode 100644 index 0000000..f64169b --- /dev/null +++ b/data/may22.csv @@ -0,0 +1,23 @@ +month,Cost center,GL account,Plan,Actual +202205,IT,Salaries,30000,31000 +202205,IT,Business travel,5000,6000 +202205,IT,Consultancy,10000,10500 +202205,IT,Training costs,5000,5000 +202205,HR local support,Salaries,15000,13000 +202205,HR local support,Events,10000,9000 +202205,HR local support,Training costs,2000,2700 +202205,Finance,Salaries,13000,15000 +202205,Finance,Consultancy,6000,5000 +202205,Finance,Training costs,500,500 +202205,Purchasing,Salaries,40000,40000 +202205,Purchasing,Consultancy,2000,1500 +202205,Purchasing,Training costs,1000,1500 +202205,HR services,Salaries,35000,39000 +202205,HR services,Business travel,10000,9000 +202205,HR services,Training costs,2000,1000 +202205,Public Relations,Salaries,10000,10000 +202205,Public Relations,Events,18000,19200 +202205,Public Relations,Business travel,2000,1000 +202205,Public Relations,Training costs,600,600 +202205,Facility management,Office lease,50000,50000 +202205,Facility management,Salaries,5000,4000 \ No newline at end of file diff --git a/data/may22.xlsx b/data/may22.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..182957d4a6118fe17846c0a7e76722cc8eab6deb GIT binary patch literal 9482 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 He}4Nvl-HkG literal 0 HcmV?d00001 diff --git a/data/months.RDS b/data/months.RDS new file mode 100644 index 0000000000000000000000000000000000000000..52d59376405483e54c78f7e5b83b6f0c78291c3d GIT binary patch literal 61 zcmb2|=3oE==I#ec2?+^l35h9532CfGk`d0%cS>{{c;W=w8zaA{3EXFTblahZz2iL- M!n052yKIsgCw literal 0 HcmV?d00001 diff --git a/email_creds b/email_creds new file mode 100644 index 0000000..4f69aff --- /dev/null +++ b/email_creds @@ -0,0 +1 @@ +{"type":"list","attributes":{"names":{"type":"character","attributes":{},"value":["version","host","port","use_ssl","user","password"]}},"value":[{"type":"integer","attributes":{},"value":[1]},{"type":"character","attributes":{},"value":["smtp.sendgrid.net"]},{"type":"double","attributes":{},"value":[465]},{"type":"logical","attributes":{},"value":[true]},{"type":"character","attributes":{},"value":["apikey"]},{"type":"character","attributes":{},"value":["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"]}]} diff --git a/helper_server.R b/helper_server.R new file mode 100644 index 0000000..ce99722 --- /dev/null +++ b/helper_server.R @@ -0,0 +1,87 @@ + + + +# Database details + + +dsn_database <- "postgres" + +dsn_hostname <- "localhost" + +dsn_port <- "5432" + +dsn_uid <- "postgres" + +dsn_pwd <- "julley09" + + + + +# Read Cost Center etc. list from data (Changes, modification needs creating the file again and redeploying the app) + +cost.centers <- readRDS("./data/costcenters.RDS") +glaccounts <- readRDS("./data/glaccounts.RDS") +mont <- readRDS("./data/months.RDS") + +# Email settings + +# smtp <- server( +# host = "smtp.sendgrid.net", +# port = 465, +# username = "apikey", +# password = "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" +# ) + + +# Function to get data from DB + +get.db.data <- function(qry = 'SELECT * FROM calculated') { + dbGetQuery(connec, + qry) +} + +# Function to aggregate monthly deviation + +mon.dev <- function(dat) { + dat |> + 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)) +} + +# Function to aggregate last month by gl and cost + +last.mon <- function(dat) { + dat |> + filter(month == max(month)) |> + 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)) +} + + +admin.menu <- sidebarMenu( + id = "m", + menuItem("Dashboard", tabName = "dashboard", icon = icon("chart-line")), + menuItem("Upload", icon = icon("upload"), tabName = "upload"), + menuItem("Approvals", icon = icon("check"), tabName = "approvals"), + menuItem("Explanations", icon = icon("file"), tabName = "explanations"), + menuItem("Admin", icon = icon("toolbox"), tabName = "admin"), + menuItem("Contact us", icon = icon("at"), href = "https://lanubia.com/contact/") +) + +other.menu <- sidebarMenu( + id = "m", + menuItem( + "Dashboard", + tabName = "dashboard", + icon = icon("dashboard") + ), + menuItem("Explanations", icon = icon("th"), tabName = "explanations"), + menuItem("Contact us", icon = icon("id-card"), href = "https://lanubia.com/contact/") +) \ No newline at end of file diff --git a/helper_ui.R b/helper_ui.R new file mode 100644 index 0000000..3d7f7e2 --- /dev/null +++ b/helper_ui.R @@ -0,0 +1,169 @@ + +dashboard<- tabItem( + tabName = "dashboard", + autoWaiter(), + fluidRow( + column( + width = 4, + "Filter Cost Center and GL Accounts" + ), + column( + width = 4, + pickerInput( + "glaccount", + choices = glaccounts, + selected = glaccounts, + options = list( + `actions-box` = TRUE), + multiple = TRUE + ) + ), + column( + width = 4, + pickerInput( + "costcenter", + choices = cost.centers, # Getting the list from reading data from local. See helper_server + selected = cost.centers, + options = list( + `actions-box` = TRUE), + multiple = TRUE + ) + ) + ), + fluidRow( + valueUI("variation"), + valueUI("variabs"), + valueUI("actexpe"), + ), + fluidRow( + shinydashboardPlus::box( + title = "Deviation % by Month", + collapsible = TRUE, + status = "navy", + solidHeader = TRUE, + echarts4rOutput("monthlypervar",height = "300px") + ), + shinydashboardPlus::box( + title = "Abs. Deviation by Month", + collapsible = TRUE, + status = "navy", + solidHeader = TRUE, + echarts4rOutput("monthlyabsvar",height = "300px") + ) + ), + fluidRow( + shinydashboardPlus::box( + title = "Plan vs Actual", + collapsible = TRUE, + status = "navy", + solidHeader = TRUE, + sidebar = boxSidebar( + startOpen = FALSE, + id = "monthselector", + pickerInput( + "mont", + choices = mont, + selected = max(mont) + ) + ), + echarts4rOutput("monthlyabs",height = "300px") + ) + ) +) + +upload<- tabItem( + tabName = "upload", + autoWaiter(), + fluidRow( + + shinydashboardPlus::box( + title = "Fresh Upload", + collapsible = TRUE, + width = 12, + status = "navy", + solidHeader = TRUE, + fileInput( + "inpfile", + "Upload the file", + multiple = FALSE, + accept = ".csv", + width = NULL, + buttonLabel = "Browse...", + placeholder = "No file selected" + ), + datatableUI("freshdat"), + actionBttn("datsubmit","Submit") + ) + ) +) + +appndev <- tabItem( + tabName = "approvals", + autoWaiter(), + shinydashboardPlus::box( + title = "Deviations without explanation", + collapsible = TRUE, + width = 12, + status = "navy", + solidHeader = TRUE, + sidebar = boxSidebar( + startOpen = FALSE, + id = "limitselector", + sliderInput("limittoignore","Define limit", min=100, max=2000, step=100, value = 500) + ), + datatableUI("devnoexp"), + actionBttn("notify","Notify") + ), + shinydashboardPlus::box( + title = "Awaiting Approval", + collapsible = TRUE, + width = 12, + status = "navy", + solidHeader = TRUE, + excelOutput("expnoapp"), + actionBttn("approvalsubmit","Submit") + ) + +) + +explan <- tabItem( + tabName = "explanations", + autoWaiter(), + shinydashboardPlus::box( + title = "Explanations Pending", + collapsible = TRUE, + width = 12, + status = "navy", + solidHeader = TRUE, + excelOutput("explanationtofill"), + actionBttn("explanationsubmit","Submit") + ) + + +) + +admin<-tabItem( + tabName = "admin", + autoWaiter(), + shinydashboardPlus::box( + title = "Email IDs", + width = 8, + collapsible = TRUE, + status = "navy", + solidHeader = TRUE, + excelOutput("emailtable"), + actionBttn("emailsubmit","Submit") + ), + shinydashboardPlus::box( + title = "Deviation Rules", + width = 4, + collapsible = TRUE, + status = "navy", + solidHeader = TRUE, + excelOutput("limitable"), + actionBttn("limitsubmit","Submit") + ) + +) + + diff --git a/mod_datatable.R b/mod_datatable.R new file mode 100644 index 0000000..11fd227 --- /dev/null +++ b/mod_datatable.R @@ -0,0 +1,29 @@ +datatableUI<- function(id){ + ns<-NS(id) + DT::dataTableOutput(ns("dtable")) +} + +datatableServer<-function(id, dat){ + moduleServer( + id, + function(input,output,session){ + output$dtable<- renderDataTable({ + validate(need(nrow(dat())>0, "No data")) + datatable( + dat(), + extensions = "Buttons", + options = list( + paging = TRUE, + scrollX = TRUE, + searching = TRUE, + ordering = TRUE, + dom = 'Bfrtip', + buttons = c('copy', 'csv', 'excel', 'pdf'), + pageLength = 10, + lengthMenu = c(3, 5, 10) + ) + ) + }) + } + ) +} \ No newline at end of file diff --git a/mod_elinearea.R b/mod_elinearea.R new file mode 100644 index 0000000..c8baa45 --- /dev/null +++ b/mod_elinearea.R @@ -0,0 +1,32 @@ +elineareaUI<- function(id){ + ns<-NS(id) + echarts4rOutput("elaplot") +} + +elineareaServer<-function(id, dat){ + moduleServer( + id, + function(input,output,session){ + output$elaplot<- renderEcharts4r({ + dat |> + e_chart(x=month) |> + e_line(serie = deviation.percent, smooth=T, color="cyan") |> + e_area(serie = deviation.percent, smooth=T, color="cyan") |> + e_axis_labels(x = "month", y="Deviation") |> + e_format_y_axis(suffix = " %") |> + e_title("Deviation", "Selected Cost Centers") |> + e_tooltip(formatter = htmlwidgets::JS(" + function(params){ + return('Month: ' + params.value[0] + '
Deviation: ' + params.value[1] + '%') + } + ") + ) |> + e_legend(right = 100) |> + e_datazoom(x_index = c(0, 1)) |> + e_toolbox_feature(feature = c("saveAsImage","dataView")) |> + e_theme("forest") + + }) + } + ) +} \ No newline at end of file diff --git a/mod_step.R b/mod_step.R new file mode 100644 index 0000000..c30b92e --- /dev/null +++ b/mod_step.R @@ -0,0 +1,27 @@ +stepUI<- function(id){ + ns<-NS(id) + echarts4rOutput("elaplot") +} + +stepServer<-function(id, dat){ + moduleServer( + id, + function(input,output,session){ + output$elaplot<- renderEcharts4r({ + dat |> + e_charts(cost_gl) |> + e_bar(Plan, name = "Plan", color="gray") |> + e_step(Actual, name = "Actual", color="red") |> + e_axis_labels(x = "GL+Cost Center", y="Deviation") |> + e_title("Selected Cost Centers") |> + e_tooltip() |> + e_legend(right = 100) |> + e_datazoom(x_index = 0, type = "slider") |> + e_datazoom(y_index = 0, type = "slider") |> + e_toolbox_feature(feature = c("saveAsImage","dataView")) |> + e_theme("chalk") + + }) + } + ) +} \ No newline at end of file diff --git a/mod_valuebox.R b/mod_valuebox.R new file mode 100644 index 0000000..06a832e --- /dev/null +++ b/mod_valuebox.R @@ -0,0 +1,24 @@ + +valueUI<- function(id){ + ns<-NS(id) + valueBoxOutput(ns("vbox")) +} + +valueServer<-function(id, ttl="title",n,icn="credit-card",clr="red",symbl){ + moduleServer( + id, + function(input,output,session){ + #print(n()) + output$vbox<- renderValueBox({ + valueBox( + paste0(n," ", symbl), + ttl, + icon=icon(icn), + color = clr + ) + }) + + + } + ) +} \ No newline at end of file diff --git a/server.R b/server.R new file mode 100644 index 0000000..d223826 --- /dev/null +++ b/server.R @@ -0,0 +1,568 @@ +# 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() + } + + } + } + }) + + +}) diff --git a/ui.R b/ui.R new file mode 100644 index 0000000..df15649 --- /dev/null +++ b/ui.R @@ -0,0 +1,125 @@ +# +# Custom App developed for Finance Team of Aramco +# ::Asitav Sen:: +# ::LaNubia Consulting:: +# ::asitav.sen@lanubia.com:: +# + +library(shiny) +library(shinydashboard) +library(countup) +library(shinyWidgets) +library(shinydashboardPlus) +library(DBI) +library(RPostgres) +library(dplyr) +library(echarts4r) +library(lubridate) +library(DT) +library(excelR) +library(blastula) +library(shinymanager) +library(glue) +library(shinythemes) +library(kableExtra) +library(waiter) + + + +source("helper_server.R") +source("mod_valuebox.R") +source("mod_elinearea.R") +source("mod_step.R") +source("mod_datatable.R") +source("helper_ui.R") + +# Define UI for application + + shinyUI( + secure_app( + shinydashboardPlus::dashboardPage( + title = "Budgetrack", + #skin = "blue-light", + #skin = "midnight", + header = shinydashboardPlus::dashboardHeader(title = "Budgetrack"), + sidebar = shinydashboardPlus::dashboardSidebar( + sidebarMenuOutput("menu") + ), + body = dashboardBody( + + tags$head(tags$style(HTML( + + ' + /* logo */ + .skin-blue .main-header .logo { + background-color: #0477ci; + } + + /* logo when hovered */ + .skin-blue .main-header .logo:hover { + background-color: #009adc; + } + + /* navbar (rest of the header) */ + .skin-blue .main-header .navbar { + background-color: #0033a0; + } + + /* main sidebar */ + .skin-blue .main-sidebar { + background-color: #ffffff; + } + + /* active selected tab in the sidebarmenu */ + .skin-blue .main-sidebar .sidebar .sidebar-menu .active a{ + background-color: #01a54b; + } + + /* other links in the sidebarmenu */ + .skin-blue .main-sidebar .sidebar .sidebar-menu a{ + background-color: #ffffff; + color: #000000; + } + + /* other links in the sidebarmenu when hovered */ + .skin-blue .main-sidebar .sidebar .sidebar-menu a:hover{ + background-color: #009adc; + } + ' + + ))), + tabItems(dashboard, + upload, + appndev, + explan, + admin)) + ), + enable_admin = TRUE, + theme = shinythemes::shinytheme("united"), + tags_top = + tags$div( + tags$h4("Created exclusively for ", style ="align:center"), + br(), + tags$img( + src = "https://www.aramco.com/images/affiliateLogo-2x.png", width = 100 + ), + br(), + br(), + tags$h4("By", style ="align:center"), + tags$img(src="logo.png", width=100) + ), + tags_bottom = tags$p( + "For any question, please contact", + tags$a( + href ="mailto:asitav.sen@lanubia.com?Subject=Aramco%20aBugdet", + target="_top","Asitav Sen" + ) + ), + background = "linear-gradient(225deg,rgb(0,163,224), + rgb(0,51,160), + rgb(0,132,61), + rgb(132,189,0));" + ) + +) + diff --git a/www/loader.gif b/www/loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..738cd764be4f6742fc8da011edfbae12c52e09f1 GIT binary patch literal 69769 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 literal 0 HcmV?d00001 diff --git a/www/logo.png b/www/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..632c677c332f47dd65d11a0098c6fdd50f8b2c33 GIT binary patch literal 11645 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 literal 0 HcmV?d00001