diff --git a/cmd/web/web.go b/cmd/web/web.go
index 4b3a58e..ff71904 100644
--- a/cmd/web/web.go
+++ b/cmd/web/web.go
@@ -1,6 +1,7 @@
package main
import (
+ "github.com/datasektionen/GOrdian/internal/config"
"github.com/datasektionen/GOrdian/internal/database"
"log"
"net/http"
@@ -9,11 +10,18 @@ import (
)
func main() {
- db, err := database.Connect()
+ envVar := config.GetEnv()
+ dbGO, err := database.Connect(envVar.PsqlconnStringGOrdian)
if err != nil {
- log.Printf("error accessing database: %v", err)
+ log.Printf("error accessing GOrdian database: %v", err)
}
- if err := web.Mount(http.DefaultServeMux, db); err != nil {
+
+ dbCF, err := database.Connect(envVar.PsqlconnStringCashflow)
+ if err != nil {
+ log.Printf("error accessing Cashflow database: %v", err)
+ }
+
+ if err := web.Mount(http.DefaultServeMux, web.Databases{DBCF: dbCF, DBGO: dbGO}); err != nil {
panic(err)
}
panic(http.ListenAndServe("0.0.0.0:3000", nil))
diff --git a/internal/config/env.go b/internal/config/env.go
index 9f45b3b..ac5ec8a 100644
--- a/internal/config/env.go
+++ b/internal/config/env.go
@@ -3,47 +3,39 @@ package config
import "os"
type EnvVar struct {
- DBHost string
- DBPort string
- DBUser string
- DBPass string
- DBName string
- LoginURL string
- LoginToken string
- PlsURL string
- PlsSystem string
- ServerPort string
- ServerURL string
+ PsqlconnStringGOrdian string
+ PsqlconnStringCashflow string
+ LoginURL string
+ LoginToken string
+ PlsURL string
+ PlsSystem string
+ ServerPort string
+ ServerURL string
}
func GetEnv() EnvVar {
envConfig := EnvVar{
- DBHost: os.Getenv("DB_HOST"),
- DBPort: os.Getenv("DB_PORT"),
- DBUser: os.Getenv("DB_USER"),
- DBPass: os.Getenv("DB_PASS"),
- DBName: os.Getenv("DB_NAME"),
- LoginURL: os.Getenv("LOGIN_URL"),
- LoginToken: os.Getenv("LOGIN_TOKEN"),
- PlsURL: os.Getenv("PLS_URL"),
- PlsSystem: os.Getenv("PLS_SYSTEM"),
- ServerPort: os.Getenv("SERVER_PORT"),
- ServerURL: os.Getenv("SERVER_URL"),
+ //prod
+ PsqlconnStringGOrdian: os.Getenv("GO_CONN"),
+ PsqlconnStringCashflow: os.Getenv("CF_CONN"),
+ LoginURL: os.Getenv("LOGIN_URL"),
+ LoginToken: os.Getenv("LOGIN_TOKEN"),
+ PlsURL: os.Getenv("PLS_URL"),
+ PlsSystem: os.Getenv("PLS_SYSTEM"),
+ ServerPort: os.Getenv("SERVER_PORT"),
+ ServerURL: os.Getenv("SERVER_URL"),
+
+ // local
+ // PsqlconnStringGOrdian: "host=localhost port=5432 user=alexander password=kopis dbname=budget_local sslmode=disable",
+ // PsqlconnStringCashflow: "host=localhost port=54321 user=cashflow password=cashflow dbname=cashflow sslmode=disable",
+ // LoginURL: "https://login.datasektionen.se",
+ // PlsURL: "https://pls.datasektionen.se",
+ // PlsSystem: "gordian",
+ // ServerPort: "3000",
+ // ServerURL: "http://localhost:3000",
+ // LoginToken: "this is secret fuck you",
}
return envConfig
}
-
-//example
-// DBHost: localhost,
-// DBPort: "5432",
-// DBUser: "alexander",
-// DBPass: "kopis",
-// DBName: "budget_local",
-// LoginURL: "https://login.datasektionen.se",
-// LoginToken: "this is secret fuck you",
-// PlsURL: "https://pls.datasektionen.se",
-// PlsSystem: "gordian",
-// ServerPort: "3000",
-// ServerURL: "http://localhost:3000",
diff --git a/internal/database/loader.go b/internal/database/loader.go
index 1c46f83..af674fd 100644
--- a/internal/database/loader.go
+++ b/internal/database/loader.go
@@ -1,22 +1,19 @@
package database
import (
+ "database/sql"
"fmt"
"github.com/datasektionen/GOrdian/internal/excel"
"io"
)
-func SaveBudget(fileReader io.Reader) error {
+func SaveBudget(fileReader io.Reader, db *sql.DB) error {
fmt.Println("You have very many money")
//testBudget := "test/Budget_2024.xlsx"
costCentres, secondaryCostCentres, budgetLines, err := excel.ReadExcel(fileReader)
- if err != nil {
- return fmt.Errorf("error parsing Excel file: %v", err)
- }
- db, err := Connect()
if err != nil {
- return fmt.Errorf("error accessing database: %v", err)
+ return fmt.Errorf("error parsing Excel file: %v", err)
}
err = WipeDatabase(db)
diff --git a/internal/database/postgres.go b/internal/database/postgres.go
index 6b7132d..fc94971 100644
--- a/internal/database/postgres.go
+++ b/internal/database/postgres.go
@@ -3,16 +3,10 @@ package database
import (
"database/sql"
"fmt"
- "github.com/datasektionen/GOrdian/internal/config"
_ "github.com/lib/pq"
)
-func Connect() (*sql.DB, error) {
-
- envVar := config.GetEnv()
-
- psqlconnString := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
- envVar.DBHost, envVar.DBPort, envVar.DBUser, envVar.DBPass, envVar.DBName)
+func Connect(psqlconnString string) (*sql.DB, error) {
db, err := sql.Open("postgres", psqlconnString)
if err != nil {
diff --git a/internal/web/DBgetters.go b/internal/web/DBgetters.go
new file mode 100644
index 0000000..ce0c6d5
--- /dev/null
+++ b/internal/web/DBgetters.go
@@ -0,0 +1,149 @@
+package web
+
+import (
+ "database/sql"
+ "fmt"
+ "github.com/datasektionen/GOrdian/internal/excel"
+)
+
+func getBudgetLinesByCostCentreID(db *sql.DB, costCentreID int) ([]excel.BudgetLine, error) {
+ var budgetLinesGetStatementStatic = `
+ SELECT
+ budget_lines.id,
+ budget_lines.name,
+ income,
+ expense,
+ comment,
+ account,
+ secondary_cost_centres.id,
+ secondary_cost_centres.name
+ FROM budget_lines
+ JOIN secondary_cost_centres ON secondary_cost_centres.id = secondary_cost_centre_id
+ WHERE cost_centre_id = $1
+ ORDER BY secondary_cost_centre_id
+ `
+ result, err := db.Query(budgetLinesGetStatementStatic, costCentreID)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get budget lines from database: %v", err)
+ }
+ var budgetLines []excel.BudgetLine
+ for result.Next() {
+ var budgetLine excel.BudgetLine
+
+ err := result.Scan(
+ &budgetLine.BudgetLineID,
+ &budgetLine.BudgetLineName,
+ &budgetLine.BudgetLineIncome,
+ &budgetLine.BudgetLineExpense,
+ &budgetLine.BudgetLineComment,
+ &budgetLine.BudgetLineAccount,
+ &budgetLine.SecondaryCostCentreID,
+ &budgetLine.SecondaryCostCentreName,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to scan budget line from query result: %v", err)
+ }
+ budgetLines = append(budgetLines, budgetLine)
+ }
+ return budgetLines, nil
+}
+
+func getCostCentres(db *sql.DB) ([]excel.CostCentre, error) {
+ var costCentresGetStatementStatic = `SELECT id, name, type FROM cost_centres ORDER BY name`
+ result, err := db.Query(costCentresGetStatementStatic)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get cost centres from database: %v", err)
+ }
+ var costCentres []excel.CostCentre
+ for result.Next() {
+ var costCentre excel.CostCentre
+
+ err := result.Scan(&costCentre.CostCentreID, &costCentre.CostCentreName, &costCentre.CostCentreType)
+ if err != nil {
+ return nil, fmt.Errorf("failed to scan cost centre from query result: %v", err)
+ }
+ costCentres = append(costCentres, costCentre)
+ }
+ return costCentres, nil
+}
+
+func getCostCentreByID(db *sql.DB, costCentreID int) (excel.CostCentre, error) {
+ var costCentreGetStatementStatic = `SELECT id, name, type FROM cost_centres WHERE id = $1`
+ result := db.QueryRow(costCentreGetStatementStatic, costCentreID)
+ var costCentre excel.CostCentre
+ err := result.Scan(&costCentre.CostCentreID, &costCentre.CostCentreName, &costCentre.CostCentreType)
+ if err != nil {
+ return excel.CostCentre{}, fmt.Errorf("failed to scan cost centre from query result: %v", err)
+ }
+ return costCentre, nil
+}
+
+func getSecondaryCostCentresByCostCentreID(db *sql.DB, costCentreID int) ([]excel.SecondaryCostCentre, error) {
+ var SecondaryCostCentresGetStatementStatic = `
+ SELECT
+ id,
+ name,
+ cost_centre_id
+ FROM secondary_cost_centres
+ WHERE cost_centre_id = $1
+ ORDER BY id
+ `
+ result, err := db.Query(SecondaryCostCentresGetStatementStatic, costCentreID)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get secondary cost centres from database: %v", err)
+ }
+ var secondaryCostCentres []excel.SecondaryCostCentre
+ for result.Next() {
+ var secondaryCostCentre excel.SecondaryCostCentre
+
+ err := result.Scan(
+ &secondaryCostCentre.SecondaryCostCentreID,
+ &secondaryCostCentre.SecondaryCostCentreName,
+ &secondaryCostCentre.CostCentreID,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to scan secondary cost centre from query result: %v", err)
+ }
+ secondaryCostCentres = append(secondaryCostCentres, secondaryCostCentre)
+ }
+ return secondaryCostCentres, nil
+}
+
+func getBudgetLinesBySecondaryCostCentreID(db *sql.DB, secondaryCostCentreID int) ([]excel.BudgetLine, error) {
+ var budgetLinesGetStatementStatic = `
+ SELECT
+ id,
+ name,
+ income,
+ expense,
+ comment,
+ account,
+ secondary_cost_centre_id
+ FROM budget_lines
+ WHERE secondary_cost_centre_id = $1
+ ORDER BY id
+ `
+ result, err := db.Query(budgetLinesGetStatementStatic, secondaryCostCentreID)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get budgetlines from database: %v", err)
+ }
+ var budgetLines []excel.BudgetLine
+ for result.Next() {
+ var budgetLine excel.BudgetLine
+
+ err := result.Scan(
+ &budgetLine.BudgetLineID,
+ &budgetLine.BudgetLineName,
+ &budgetLine.BudgetLineIncome,
+ &budgetLine.BudgetLineExpense,
+ &budgetLine.BudgetLineComment,
+ &budgetLine.BudgetLineAccount,
+ &budgetLine.SecondaryCostCentreID,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to scan budget line from query result: %v", err)
+ }
+ budgetLines = append(budgetLines, budgetLine)
+ }
+ return budgetLines, nil
+}
diff --git a/internal/web/api.go b/internal/web/api.go
new file mode 100644
index 0000000..ef8ddc6
--- /dev/null
+++ b/internal/web/api.go
@@ -0,0 +1,53 @@
+package web
+
+import (
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strconv"
+)
+
+func apiCostCentres(w http.ResponseWriter, r *http.Request, db *sql.DB) error {
+ costCentres, err := getCostCentres(db)
+ if err != nil {
+ return fmt.Errorf("failed get scan cost centres information from database: %v", err)
+ }
+ err = json.NewEncoder(w).Encode(costCentres)
+ if err != nil {
+ return fmt.Errorf("failed to encode cost centres to json: %v", err)
+ }
+ return nil
+}
+
+func apiSecondaryCostCentre(w http.ResponseWriter, r *http.Request, db *sql.DB) error {
+ idCC, err := strconv.Atoi(r.FormValue("id"))
+ if err != nil {
+ return fmt.Errorf("failed to convert secondary cost centre id to int: %v", err)
+ }
+ secondaryCostCentres, err := getSecondaryCostCentresByCostCentreID(db, idCC)
+ if err != nil {
+ return fmt.Errorf("failed get scan sendondary cost centres information from database: %v", err)
+ }
+ err = json.NewEncoder(w).Encode(secondaryCostCentres)
+ if err != nil {
+ return fmt.Errorf("failed to encode secondary cost centres to json: %v", err)
+ }
+ return nil
+}
+
+func apiBudgetLine(w http.ResponseWriter, r *http.Request, db *sql.DB) error {
+ idSCC, err := strconv.Atoi(r.FormValue("id"))
+ if err != nil {
+ return fmt.Errorf("failed to convert SCC id fromstring to int: %v", err)
+ }
+ budgetLines, err := getBudgetLinesBySecondaryCostCentreID(db, idSCC)
+ if err != nil {
+ return fmt.Errorf("failed get scan budget lines information from database: %v", err)
+ }
+ err = json.NewEncoder(w).Encode(budgetLines)
+ if err != nil {
+ return fmt.Errorf("failed to encode budget lines to json: %v", err)
+ }
+ return nil
+}
diff --git a/internal/web/costcentre.go b/internal/web/costcentre.go
new file mode 100644
index 0000000..64eb3f4
--- /dev/null
+++ b/internal/web/costcentre.go
@@ -0,0 +1,102 @@
+package web
+
+import (
+ "database/sql"
+ "fmt"
+ "github.com/datasektionen/GOrdian/internal/excel"
+ "net/http"
+ "strconv"
+)
+
+func costCentrePage(w http.ResponseWriter, r *http.Request, db *sql.DB, perms []string, loggedIn bool) error {
+ costCentreIDString := r.PathValue("costCentreIDPath")
+ costCentreIDInt, err := strconv.Atoi(costCentreIDString)
+ if err != nil {
+ return fmt.Errorf("failed to convert cost centre id from string to int: %v", err)
+ }
+
+ budgetLines, err := getBudgetLinesByCostCentreID(db, costCentreIDInt)
+ if err != nil {
+ return fmt.Errorf("failed get scan budget line information from database: %v", err)
+ }
+
+ //omg
+ secondaryCostCentresWithBudgetLinesList := make([]secondaryCostCentresWithBudgetLines, 1)
+ currentSecondaryCostCentre := &secondaryCostCentresWithBudgetLinesList[0]
+ for _, budgetLine := range budgetLines {
+ if currentSecondaryCostCentre.SecondaryCostCentreName != budgetLine.SecondaryCostCentreName {
+ secondaryCostCentresWithBudgetLinesList = append(secondaryCostCentresWithBudgetLinesList, secondaryCostCentresWithBudgetLines{
+ SecondaryCostCentreName: budgetLine.SecondaryCostCentreName,
+ BudgetLines: []excel.BudgetLine{},
+ })
+ currentSecondaryCostCentre = &secondaryCostCentresWithBudgetLinesList[len(secondaryCostCentresWithBudgetLinesList)-1]
+ }
+ currentSecondaryCostCentre.BudgetLines = append(currentSecondaryCostCentre.BudgetLines, budgetLine)
+ }
+ secondaryCostCentresWithBudgetLinesList = secondaryCostCentresWithBudgetLinesList[1:]
+
+ costCentre, err := getCostCentreByID(db, costCentreIDInt)
+ if err != nil {
+ return fmt.Errorf("failed get scan cost centre information from database: %v", err)
+ }
+
+ //calc the total incomes, expenses and results of all cost centres in the list
+ secondaryCostCentresWithBudgetLinesList, err = calculateSecondaryCostCentres(secondaryCostCentresWithBudgetLinesList)
+ if err != nil {
+ return fmt.Errorf("failed calculate secondary cost centre values: %v", err)
+ }
+
+ costCentreTotalIncome, costCentreTotalExpense, costCentreTotalResult, err := calculateCostCentre(secondaryCostCentresWithBudgetLinesList)
+ if err != nil {
+ return fmt.Errorf("failed calculate cost centre values: %v", err)
+ }
+
+ if err := templates.ExecuteTemplate(w, "costcentre.gohtml", map[string]any{
+ "motd": motdGenerator(),
+ "secondaryCostCentresWithBudgetLinesList": secondaryCostCentresWithBudgetLinesList,
+ "costCentre": costCentre,
+ "costCentreTotalIncome": costCentreTotalIncome,
+ "costCentreTotalExpense": costCentreTotalExpense,
+ "costCentreTotalResult": costCentreTotalResult,
+ "permissions": perms,
+ "loggedIn": loggedIn,
+ }); err != nil {
+ return fmt.Errorf("could not render template: %w", err)
+ }
+ return nil
+}
+
+func calculateCostCentre(secondaryCostCentresWithBudgetLinesList []secondaryCostCentresWithBudgetLines) (int, int, int, error) {
+ var totalIncome int
+ var totalExpense int
+ for _, sCCWithBudgetLines := range secondaryCostCentresWithBudgetLinesList {
+ totalIncome = totalIncome + sCCWithBudgetLines.SecondaryCostCentreTotalIncome
+ totalExpense = totalExpense + sCCWithBudgetLines.SecondaryCostCentreTotalExpense
+ }
+ totalResult := totalIncome + totalExpense
+
+ return totalIncome, totalExpense, totalResult, nil
+}
+
+func calculateSecondaryCostCentres(secondaryCostCentresWithBudgetLinesList []secondaryCostCentresWithBudgetLines) ([]secondaryCostCentresWithBudgetLines, error) {
+ for index, sCCWithBudgetLines := range secondaryCostCentresWithBudgetLinesList {
+ var totalIncome int
+ var totalExpense int
+ for _, budgetLine := range sCCWithBudgetLines.BudgetLines {
+ totalIncome = totalIncome + budgetLine.BudgetLineIncome
+ totalExpense = totalExpense + budgetLine.BudgetLineExpense
+ }
+ secondaryCostCentresWithBudgetLinesList[index].SecondaryCostCentreTotalIncome = totalIncome
+ secondaryCostCentresWithBudgetLinesList[index].SecondaryCostCentreTotalExpense = totalExpense
+ secondaryCostCentresWithBudgetLinesList[index].SecondaryCostCentreTotalResult = totalIncome + totalExpense
+ }
+ return secondaryCostCentresWithBudgetLinesList, nil
+}
+
+type secondaryCostCentresWithBudgetLines struct {
+ SecondaryCostCentreName string
+ SecondaryCostCentreTotalIncome int
+ SecondaryCostCentreTotalExpense int
+ SecondaryCostCentreTotalResult int
+ BudgetLines []excel.BudgetLine
+}
diff --git a/internal/web/frame.go b/internal/web/frame.go
new file mode 100644
index 0000000..01770a5
--- /dev/null
+++ b/internal/web/frame.go
@@ -0,0 +1,163 @@
+package web
+
+import (
+ "database/sql"
+ "fmt"
+ "net/http"
+
+ "github.com/datasektionen/GOrdian/internal/excel"
+)
+
+type FrameLine struct {
+ FrameLineName string
+ FrameLineIncome int
+ FrameLineExpense int
+ FrameLineInternal int
+ FrameLineResult int
+}
+
+func framePage(w http.ResponseWriter, r *http.Request, db *sql.DB, perms []string, loggedIn bool) error {
+ budgetLines, err := getFrameLines(db)
+ if err != nil {
+ return fmt.Errorf("failed get scan budget lines information from database: %v", err)
+ }
+ committeeFrameLines, projectFrameLines, otherFrameLines, totalFrameLine, sumCommitteeFrameLine, sumProjectFrameLine, sumOtherFrameLine, err := generateFrameLines(budgetLines)
+ if err != nil {
+ return fmt.Errorf("failed to generate frame budget lines: %v", err)
+ }
+ if err := templates.ExecuteTemplate(w, "frame.gohtml", map[string]any{
+ "motd": motdGenerator(),
+ "committeeframelines": committeeFrameLines,
+ "projectframelines": projectFrameLines,
+ "otherframelines": otherFrameLines,
+ "totalframeline": totalFrameLine,
+ "sumcommitteeframeline": sumCommitteeFrameLine,
+ "sumprojectframeline": sumProjectFrameLine,
+ "sumotherframeline": sumOtherFrameLine,
+ "permissions": perms,
+ "loggedIn": loggedIn,
+ }); err != nil {
+ return fmt.Errorf("could not render template: %w", err)
+ }
+ return nil
+}
+
+func getFrameLines(db *sql.DB) ([]excel.BudgetLine, error) {
+ var frameLinesGetStatementStatic = `
+ SELECT
+ SUM(income),
+ SUM(expense),
+ secondary_cost_centres.name ILIKE '%Internt%',
+ cost_centres.id,
+ cost_centres.name,
+ cost_centres.type
+ FROM budget_lines
+ JOIN secondary_cost_centres ON secondary_cost_centres.id = secondary_cost_centre_id
+ JOIN cost_centres ON secondary_cost_centres.cost_centre_id = cost_centres.id
+ GROUP BY cost_centres.id, cost_centres.name, cost_centres.type, secondary_cost_centres.name ILIKE '%Internt%'
+ ORDER BY cost_centres.name, secondary_cost_centres.name ILIKE '%Internt%'
+ `
+ result, err := db.Query(frameLinesGetStatementStatic)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get framelines from database: %v", err)
+ }
+ var frameLines []excel.BudgetLine
+ for result.Next() {
+ var frameLine excel.BudgetLine
+
+ err := result.Scan(
+ &frameLine.BudgetLineIncome,
+ &frameLine.BudgetLineExpense,
+ &frameLine.SecondaryCostCentreName,
+ &frameLine.CostCentreID,
+ &frameLine.CostCentreName,
+ &frameLine.CostCentreType,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to scan budget line from query result: %v", err)
+ }
+ frameLines = append(frameLines, frameLine)
+ }
+ return frameLines, nil
+}
+
+func generateFrameLines(frameLines []excel.BudgetLine) ([]FrameLine, []FrameLine, []FrameLine, FrameLine, FrameLine, FrameLine, FrameLine, error) {
+ var committeeFrameLines []FrameLine
+ var projectFrameLines []FrameLine
+ var otherFrameLines []FrameLine
+ var totalFrameLine FrameLine
+ var sumCommitteeFrameLine FrameLine
+ var sumProjectFrameLine FrameLine
+ var sumOtherFrameLine FrameLine
+
+ totalFrameLine.FrameLineName = "Totalt"
+ sumCommitteeFrameLine.FrameLineName = "Summa nämnder"
+ sumProjectFrameLine.FrameLineName = "Summa projekt"
+ sumOtherFrameLine.FrameLineName = "Summa övrigt"
+
+ var skippidi bool
+ for i, frameLine := range frameLines {
+ if skippidi {
+ skippidi = false
+ continue
+ }
+ frameLineIncome := frameLine.BudgetLineIncome
+ frameLineExpense := frameLine.BudgetLineExpense
+ frameLineName := frameLine.CostCentreName
+ frameLineInternal := 0
+ frameLineResult := 0
+
+ // each CC appears twice, once for internal costs, once for the rest
+ // both are handled i and i+1
+ // skippidi makes sure that the loop incements by two
+ if i+1 < len(frameLines) && frameLines[i+1].CostCentreName == frameLineName {
+ frameLineIncome += frameLines[i+1].BudgetLineIncome
+ frameLineExpense += frameLines[i+1].BudgetLineExpense
+ frameLineInternal = frameLines[i+1].BudgetLineIncome + frameLines[i+1].BudgetLineExpense
+ skippidi = true
+ }
+
+ frameLineResult = frameLineIncome + frameLineExpense
+
+ reconstructedFrameLine := FrameLine{frameLineName, frameLineIncome, frameLineExpense, frameLineInternal, frameLineResult}
+
+ totalFrameLine.FrameLineIncome += frameLineIncome
+ totalFrameLine.FrameLineExpense += frameLineExpense
+ totalFrameLine.FrameLineInternal += frameLineInternal
+ totalFrameLine.FrameLineResult += frameLineResult
+
+ switch frameLine.CostCentreType {
+ case "committee":
+ committeeFrameLines = append(committeeFrameLines, reconstructedFrameLine)
+ case "project":
+ projectFrameLines = append(projectFrameLines, reconstructedFrameLine)
+ case "other":
+ otherFrameLines = append(otherFrameLines, reconstructedFrameLine)
+ default:
+ return nil, nil, nil, FrameLine{}, FrameLine{}, FrameLine{}, FrameLine{}, fmt.Errorf("faulty cost centre type found when splitting")
+ }
+ }
+
+ for _, committeeFrameLine := range committeeFrameLines {
+ sumCommitteeFrameLine.FrameLineIncome += committeeFrameLine.FrameLineIncome
+ sumCommitteeFrameLine.FrameLineExpense += committeeFrameLine.FrameLineExpense
+ sumCommitteeFrameLine.FrameLineInternal += committeeFrameLine.FrameLineInternal
+ sumCommitteeFrameLine.FrameLineResult += committeeFrameLine.FrameLineResult
+ }
+
+ for _, ProjectFrameLine := range projectFrameLines {
+ sumProjectFrameLine.FrameLineIncome += ProjectFrameLine.FrameLineIncome
+ sumProjectFrameLine.FrameLineExpense += ProjectFrameLine.FrameLineExpense
+ sumProjectFrameLine.FrameLineInternal += ProjectFrameLine.FrameLineInternal
+ sumProjectFrameLine.FrameLineResult += ProjectFrameLine.FrameLineResult
+ }
+
+ for _, OtherFrameLine := range otherFrameLines {
+ sumOtherFrameLine.FrameLineIncome += OtherFrameLine.FrameLineIncome
+ sumOtherFrameLine.FrameLineExpense += OtherFrameLine.FrameLineExpense
+ sumOtherFrameLine.FrameLineInternal += OtherFrameLine.FrameLineInternal
+ sumOtherFrameLine.FrameLineResult += OtherFrameLine.FrameLineResult
+ }
+
+ return committeeFrameLines, projectFrameLines, otherFrameLines, totalFrameLine, sumCommitteeFrameLine, sumProjectFrameLine, sumOtherFrameLine, nil
+}
diff --git a/internal/web/index.go b/internal/web/index.go
new file mode 100644
index 0000000..1332816
--- /dev/null
+++ b/internal/web/index.go
@@ -0,0 +1,86 @@
+package web
+
+import (
+ "database/sql"
+ "fmt"
+ "io"
+ "log/slog"
+ "net/http"
+ "strconv"
+
+ "github.com/datasektionen/GOrdian/internal/excel"
+)
+
+func indexPage(w http.ResponseWriter, r *http.Request, db *sql.DB, perms []string, loggedIn bool) error {
+ costCentres, err := getCostCentres(db)
+ if err != nil {
+ return fmt.Errorf("failed get scan cost centre information from database: %v", err)
+ }
+ committeeCostCentres, projectCostCentres, otherCostCentres, err := splitCostCentresOnType(costCentres)
+ if err != nil {
+ return fmt.Errorf("failed to split cost centres on type: %v", err)
+ }
+
+ //Mörkläggning av mottagningens budget
+ darkeningResp, err := http.Get("https://darkmode.datasektionen.se/")
+ if err != nil {
+ slog.Error("Failed to get status from darkmode", "error", err)
+ return fmt.Errorf(": %v", err)
+ }
+ defer darkeningResp.Body.Close()
+
+ if darkeningResp.StatusCode != http.StatusOK {
+ slog.Error("Status error from darkmode", "error", darkeningResp.StatusCode)
+ }
+
+ darkeningBody, err := io.ReadAll(darkeningResp.Body)
+ if err != nil {
+ slog.Error("Failed to read body", "error", err)
+ }
+
+ darkeningValue, err := strconv.ParseBool(string(darkeningBody))
+ if err != nil {
+ slog.Error("Failed to parse bool", "error", err)
+ }
+
+ if darkeningValue {
+ for index, committeeCostCentre := range committeeCostCentres {
+ if committeeCostCentre.CostCentreName == "Mottagningen" {
+ committeeCostCentres = append(committeeCostCentres[:index], committeeCostCentres[index+1:]...)
+ break
+ }
+ }
+ }
+ //end of mörkläggning
+
+ if err := templates.ExecuteTemplate(w, "index.gohtml", map[string]any{
+ "motd": motdGenerator(),
+ "committees": committeeCostCentres,
+ "projects": projectCostCentres,
+ "others": otherCostCentres,
+ "permissions": perms,
+ "loggedIn": loggedIn,
+ }); err != nil {
+ return fmt.Errorf("could not render template: %w", err)
+ }
+ return nil
+}
+
+func splitCostCentresOnType(costCentres []excel.CostCentre) ([]excel.CostCentre, []excel.CostCentre, []excel.CostCentre, error) {
+ var committeeCostCentres []excel.CostCentre
+ var projectCostCentres []excel.CostCentre
+ var otherCostCentres []excel.CostCentre
+ for _, costCentre := range costCentres {
+ switch costCentre.CostCentreType {
+ case "committee":
+ committeeCostCentres = append(committeeCostCentres, costCentre)
+ case "project":
+ projectCostCentres = append(projectCostCentres, costCentre)
+ case "other":
+ otherCostCentres = append(otherCostCentres, costCentre)
+ default:
+ return nil, nil, nil, fmt.Errorf("faulty cost centre type found when splitting")
+ }
+ }
+ return committeeCostCentres, projectCostCentres, otherCostCentres, nil
+}
diff --git a/internal/web/queries.go b/internal/web/queries.go
new file mode 100644
index 0000000..86d5595
--- /dev/null
+++ b/internal/web/queries.go
@@ -0,0 +1,182 @@
+package web
+
+var CombinedReportLinesGetStatementStatic = `
+ SELECT
+ cost_centre,
+ secondary_cost_centre,
+ budget_line,
+ SUM(amount) AS total_amount
+ FROM (
+ SELECT
+ ep.cost_centre,
+ ep.secondary_cost_centre,
+ ep.budget_line,
+ ep.amount,
+ EXTRACT(YEAR FROM e.expense_date)::text AS date, -- Cast to text
+ e.description
+ FROM expenses_expensepart AS ep
+ INNER JOIN expenses_expense AS e ON ep.expense_id = e.id
+ UNION ALL
+ SELECT
+ ip.cost_centre,
+ ip.secondary_cost_centre,
+ ip.budget_line,
+ ip.amount,
+ EXTRACT(YEAR FROM COALESCE(i.invoice_date, i.payed_at))::text AS date, -- Cast to text
+ i.description
+ FROM invoices_invoicepart AS ip
+ INNER JOIN invoices_invoice AS i ON ip.invoice_id = i.id
+ ) AS combined
+ WHERE (COALESCE($1, '') = '' OR $1 = 'Alla' OR date = $1) -- Filter by year or allow 'Alla' as wildcard
+ AND (COALESCE($2, '') = '' OR $2 = 'Alla' OR cost_centre::text = $2) -- Filter by cost_centre or allow 'Alla' as wildcard
+ GROUP BY cost_centre, secondary_cost_centre, budget_line
+ ORDER BY cost_centre, secondary_cost_centre, budget_line;
+`
+
+var uniqueCCGetStatementStatic = `
+ SELECT DISTINCT cost_centre
+ FROM (
+ SELECT
+ ep.cost_centre
+ FROM expenses_expensepart AS ep
+ INNER JOIN expenses_expense AS e ON ep.expense_id = e.id
+ UNION
+ SELECT
+ ip.cost_centre
+ FROM invoices_invoicepart AS ip
+ INNER JOIN invoices_invoice AS i ON ip.invoice_id = i.id
+ ) AS combined
+ ORDER BY cost_centre;
+ `
+
+// var ReportLinesByYearAllCCGetStatementStatic = `
+// SELECT
+// cost_centre,
+// secondary_cost_centre,
+// budget_line,
+// SUM(amount) AS total_amount
+// FROM (
+// SELECT
+// ep.cost_centre,
+// ep.secondary_cost_centre,
+// ep.budget_line,
+// ep.amount,
+// EXTRACT(YEAR FROM e.expense_date) AS date,
+// e.description
+// FROM expenses_expensepart AS ep
+// INNER JOIN expenses_expense AS e ON ep.expense_id = e.id
+// UNION ALL
+// SELECT
+// ip.cost_centre,
+// ip.secondary_cost_centre,
+// ip.budget_line,
+// ip.amount,
+// EXTRACT(YEAR FROM (COALESCE(i.invoice_date, i.payed_at))) AS invoice_date,
+// i.description
+// FROM invoices_invoicepart AS ip
+// INNER JOIN invoices_invoice AS i ON ip.invoice_id = i.id
+// ) AS combined
+// WHERE COALESCE(date = $1, true)
+// GROUP BY cost_centre, secondary_cost_centre, budget_line
+// ORDER BY cost_centre, secondary_cost_centre, budget_line;
+// `
+
+// var ReportLinesAllYearAllCCGetStatementStatic = `
+// SELECT
+// cost_centre,
+// secondary_cost_centre,
+// budget_line,
+// SUM(amount) AS total_amount
+// FROM (
+// SELECT
+// ep.cost_centre,
+// ep.secondary_cost_centre,
+// ep.budget_line,
+// ep.amount,
+// EXTRACT(YEAR FROM e.expense_date) AS date,
+// e.description
+// FROM expenses_expensepart AS ep
+// INNER JOIN expenses_expense AS e ON ep.expense_id = e.id
+// UNION ALL
+// SELECT
+// ip.cost_centre,
+// ip.secondary_cost_centre,
+// ip.budget_line,
+// ip.amount,
+// EXTRACT(YEAR FROM (COALESCE(i.invoice_date, i.payed_at))) AS invoice_date,
+// i.description
+// FROM invoices_invoicepart AS ip
+// INNER JOIN invoices_invoice AS i ON ip.invoice_id = i.id
+// ) AS combined
+// WHERE COALESCE(date = null, true)
+// GROUP BY cost_centre, secondary_cost_centre, budget_line
+// ORDER BY cost_centre, secondary_cost_centre, budget_line;
+// `
+
+// var ReportLinesAllYearByCCGetStatementStatic = `
+// SELECT
+// cost_centre,
+// secondary_cost_centre,
+// budget_line,
+// SUM(amount) AS total_amount
+// FROM (
+// SELECT
+// ep.cost_centre,
+// ep.secondary_cost_centre,
+// ep.budget_line,
+// ep.amount,
+// EXTRACT(YEAR FROM e.expense_date) AS date,
+// e.description
+// FROM expenses_expensepart AS ep
+// INNER JOIN expenses_expense AS e ON ep.expense_id = e.id
+// UNION ALL
+// SELECT
+// ip.cost_centre,
+// ip.secondary_cost_centre,
+// ip.budget_line,
+// ip.amount,
+// EXTRACT(YEAR FROM (COALESCE(i.invoice_date, i.payed_at))) AS invoice_date,
+// i.description
+// FROM invoices_invoicepart AS ip
+// INNER JOIN invoices_invoice AS i ON ip.invoice_id = i.id
+// ) AS combined
+// WHERE COALESCE(date = null, true)
+// AND COALESCE(cost_centre = $1, true)
+// GROUP BY cost_centre, secondary_cost_centre, budget_line
+// ORDER BY cost_centre, secondary_cost_centre, budget_line;
+// `
+
+// var ReportLinesByYearByCCGetStatementStatic = `
+// SELECT
+// cost_centre,
+// secondary_cost_centre,
+// budget_line,
+// SUM(amount) AS total_amount
+// FROM (
+// SELECT
+// ep.cost_centre,
+// ep.secondary_cost_centre,
+// ep.budget_line,
+// ep.amount,
+// EXTRACT(YEAR FROM e.expense_date) AS date,
+// e.description
+// FROM expenses_expensepart AS ep
+// INNER JOIN expenses_expense AS e ON ep.expense_id = e.id
+// UNION ALL
+// SELECT
+// ip.cost_centre,
+// ip.secondary_cost_centre,
+// ip.budget_line,
+// ip.amount,
+// EXTRACT(YEAR FROM (COALESCE(i.invoice_date, i.payed_at))) AS invoice_date,
+// i.description
+// FROM invoices_invoicepart AS ip
+// INNER JOIN invoices_invoice AS i ON ip.invoice_id = i.id
+// ) AS combined
+// WHERE COALESCE(date = $1, true)
+// AND COALESCE(cost_centre = $2, true)
+// GROUP BY cost_centre, secondary_cost_centre, budget_line
+// ORDER BY cost_centre, secondary_cost_centre, budget_line;
+// `
+
+
diff --git a/internal/web/report.go b/internal/web/report.go
new file mode 100644
index 0000000..ac169de
--- /dev/null
+++ b/internal/web/report.go
@@ -0,0 +1,220 @@
+package web
+
+import (
+ "database/sql"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+)
+
+type ReportLine struct {
+ ReportLineCostCentre string
+ ReportLineSecondaryCostCentre string
+ ReportLineBudgetLine string
+ ReportLineTotal string
+}
+
+type ReportBudgetLine struct {
+ BudgetLineName string
+ Total string
+}
+
+type ReportSecondaryCostCentreLine struct {
+ SecondaryCostCentreName string
+ BudgetLinesList []ReportBudgetLine
+ Total string
+}
+
+type ReportCostCentreLine struct {
+ CostCentreName string
+ SecondaryCostCentresList []ReportSecondaryCostCentreLine
+ Total string
+}
+
+func getYearsSince2017() []string {
+ startYear := 2017
+ currentYear := time.Now().Year()
+ var years []string
+
+ for year := startYear; year <= currentYear; year++ {
+ years = append(years, strconv.Itoa(year))
+ }
+
+ return years
+}
+
+func reportPage(w http.ResponseWriter, r *http.Request, db *sql.DB, perms []string, loggedIn bool) error {
+
+ currentYear := strconv.Itoa(time.Now().Year())
+
+ selectedYear := r.FormValue("year")
+ if selectedYear == "" {
+ selectedYear = currentYear
+ }
+
+ CCList, err := getCCList(db)
+ if err != nil {
+ return fmt.Errorf("failed get scan CCList information from database: %v", err)
+ }
+
+ reportLines, err := getReportLines(db, selectedYear, r.FormValue("cc"))
+ if err != nil {
+ return fmt.Errorf("failed to get scan report lines information from database: %v", err)
+ }
+
+ structuredReport, err := StructureReportLines(reportLines)
+ if err != nil {
+ return fmt.Errorf("failed to structure report lines: %v", err)
+ }
+ years := getYearsSince2017()
+
+ if err := templates.ExecuteTemplate(w, "report.gohtml", map[string]any{
+ "motd": motdGenerator(),
+ "reportLines": reportLines,
+ "permissions": perms,
+ "loggedIn": loggedIn,
+ "report": structuredReport,
+ "CCList": CCList,
+ "years": years,
+ "SelectedCC": r.FormValue("cc"),
+ "SelectedYear": selectedYear,
+ }); err != nil {
+ return fmt.Errorf("could not render template: %w", err)
+ }
+ return nil
+}
+
+func getCCList(db *sql.DB) ([]string, error) {
+ var result *sql.Rows
+ var err error
+
+ result, err = db.Query(uniqueCCGetStatementStatic)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get CCList from database: %v", err)
+ }
+ defer result.Close()
+
+ var CCList []string
+
+ for result.Next() {
+ var CC string
+
+ err := result.Scan(
+ &CC,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to scan CC from query result: %v", err)
+ }
+ CCList = append(CCList, CC)
+ }
+ return CCList, nil
+}
+
+func getReportLines(db *sql.DB, year string, cc string) ([]ReportLine, error) {
+
+ var result *sql.Rows
+ var err error
+
+ result, err = db.Query(CombinedReportLinesGetStatementStatic, year, cc)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get reportlines from database: %v", err)
+ }
+ defer result.Close()
+
+ var reportLines []ReportLine
+
+ for result.Next() {
+ var reportLine ReportLine
+
+ err := result.Scan(
+ &reportLine.ReportLineCostCentre,
+ &reportLine.ReportLineSecondaryCostCentre,
+ &reportLine.ReportLineBudgetLine,
+ &reportLine.ReportLineTotal,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to scan report line from query result: %v", err)
+ }
+ reportLines = append(reportLines, reportLine)
+ }
+ return reportLines, nil
+}
+
+// Add cost centre or return existing
+func findOrAddCostCentre(costCentres *[]ReportCostCentreLine, costCentreName string) *ReportCostCentreLine {
+ for i := range *costCentres {
+ if (*costCentres)[i].CostCentreName == costCentreName {
+ return &(*costCentres)[i]
+ }
+ }
+ // Add new cost centre if not found
+ newCostCentre := ReportCostCentreLine{
+ CostCentreName: costCentreName,
+ SecondaryCostCentresList: []ReportSecondaryCostCentreLine{},
+ Total: "0 kr",
+ }
+ *costCentres = append(*costCentres, newCostCentre)
+ return &(*costCentres)[len(*costCentres)-1]
+}
+
+// Add secondary cost centre or return existing
+func findOrAddSecondaryCostCentre(secCostCentres *[]ReportSecondaryCostCentreLine, secCostCentreName string) *ReportSecondaryCostCentreLine {
+ for i := range *secCostCentres {
+ if (*secCostCentres)[i].SecondaryCostCentreName == secCostCentreName {
+ return &(*secCostCentres)[i]
+ }
+ }
+ // Add new secondary cost centre if not found
+ newSecCostCentre := ReportSecondaryCostCentreLine{
+ SecondaryCostCentreName: secCostCentreName,
+ BudgetLinesList: []ReportBudgetLine{},
+ Total: "0 kr",
+ }
+ *secCostCentres = append(*secCostCentres, newSecCostCentre)
+ return &(*secCostCentres)[len(*secCostCentres)-1]
+}
+
+// Function to organize ReportLines into structured data
+func StructureReportLines(reportLines []ReportLine) ([]ReportCostCentreLine, error) {
+ var costCentres []ReportCostCentreLine
+
+ // Loop through each ReportLine and structure it
+ for _, line := range reportLines {
+ // Find or add the CostCentre
+ costCentre := findOrAddCostCentre(&costCentres, line.ReportLineCostCentre)
+
+ // Find or add the SecondaryCostCentre within the CostCentre
+ secCostCentre := findOrAddSecondaryCostCentre(&costCentre.SecondaryCostCentresList, line.ReportLineSecondaryCostCentre)
+
+ // Add the BudgetLine to the SecondaryCostCentre
+ secCostCentre.BudgetLinesList = append(secCostCentre.BudgetLinesList, ReportBudgetLine{
+ BudgetLineName: line.ReportLineBudgetLine,
+ Total: line.ReportLineTotal,
+ })
+ var err1, err2 error
+
+ // Update totals for SecondaryCostCentre and CostCentre
+ secCostCentre.Total, err1 = addTotals(secCostCentre.Total, line.ReportLineTotal)
+ costCentre.Total, err2 = addTotals(costCentre.Total, line.ReportLineTotal)
+
+ if err1 != nil || err2 != nil {
+ return nil, fmt.Errorf("failed to update totals for SCC or CC: %v%v", err1, err2)
+ }
+ }
+
+ return costCentres, nil
+}
+
+// Helper function to add totals (handling currency and decimal values)
+func addTotals(total1, total2 string) (string, error) {
+ t1, err1 := strconv.ParseFloat(strings.ReplaceAll(total1, " kr", ""), 64)
+ t2, err2 := strconv.ParseFloat(strings.ReplaceAll(total2, " kr", ""), 64)
+
+ if err1 != nil || err2 != nil {
+ return "0 kr", fmt.Errorf("failed operate on total float: %v%v", err1, err2)
+ }
+
+ return fmt.Sprintf("%.2f kr", t1+t2), nil
+}
diff --git a/internal/web/static/budgets/budget_2024_upd.xlsx b/internal/web/static/budgets/budget_2024_upd.xlsx
new file mode 100644
index 0000000..fd06fe7
Binary files /dev/null and b/internal/web/static/budgets/budget_2024_upd.xlsx differ
diff --git a/internal/web/templates/fippel.gohtml b/internal/web/templates/fippel.gohtml
new file mode 100644
index 0000000..221697b
--- /dev/null
+++ b/internal/web/templates/fippel.gohtml
@@ -0,0 +1,24 @@
+
\ No newline at end of file
diff --git a/internal/web/templates/frame.gohtml b/internal/web/templates/frame.gohtml
index d6e0117..462c18b 100644
--- a/internal/web/templates/frame.gohtml
+++ b/internal/web/templates/frame.gohtml
@@ -24,21 +24,21 @@
|
{{range $_, $committeeframeline := .committeeframelines}}
-
- {{$committeeframeline.FrameLineName}} |
-
- {{formatMoney $committeeframeline.FrameLineIncome}} kr
- |
-
- {{formatMoney $committeeframeline.FrameLineExpense}} kr
- |
-
- {{formatMoney $committeeframeline.FrameLineInternal}} kr
- |
-
- {{formatMoney $committeeframeline.FrameLineResult}} kr
- |
-
+
+ {{$committeeframeline.FrameLineName}} |
+
+ {{formatMoney $committeeframeline.FrameLineIncome}} kr
+ |
+
+ {{formatMoney $committeeframeline.FrameLineExpense}} kr
+ |
+
+ {{formatMoney $committeeframeline.FrameLineInternal}} kr
+ |
+
+ {{formatMoney $committeeframeline.FrameLineResult}} kr
+ |
+
{{end}}
Projekt |
diff --git a/internal/web/templates/head.gohtml b/internal/web/templates/head.gohtml
index 81ff332..97f7b3f 100644
--- a/internal/web/templates/head.gohtml
+++ b/internal/web/templates/head.gohtml
@@ -33,6 +33,10 @@
str: "Rambudget",
href: "/framebudget",
},
+ {
+ str: "Resultatrapport",
+ href: "/resultreport",
+ },
{{if sliceContains .permissions "admin" "view-all" }} {
str: "Administrera",
href: "/admin",
diff --git a/internal/web/templates/report.gohtml b/internal/web/templates/report.gohtml
new file mode 100644
index 0000000..57f343f
--- /dev/null
+++ b/internal/web/templates/report.gohtml
@@ -0,0 +1,85 @@
+
+
+{{template "head.gohtml" .}}
+
+
+
+
Resultatrapport
+
+
+
Vad har spenderats på min budgetpost?
+
+
+OBS! Räknar endast med kvitton och fakturor i Cashflow
+
+
+
+
+
Nedan kan du välja en nämnd eller ett projekt som du vill se
+
+
+
Du kan även välja ett specifikt år för att se vad som spenderades då
+
+
+
+
+
+
+
+ {{range $_, $ReportCostCentreLine := .report }}
+
+ {{$ReportCostCentreLine.CostCentreName}} |
+
+ {{$ReportCostCentreLine.Total}}
+ |
+
+ {{range $_, $ReportSecondaryCostCentreLine := $ReportCostCentreLine.SecondaryCostCentresList}}
+
+
+ {{$ReportSecondaryCostCentreLine.SecondaryCostCentreName}}
+ |
+
+ {{$ReportSecondaryCostCentreLine.Total}}
+ |
+
+ {{range $_, $ReportBudgetLine := $ReportSecondaryCostCentreLine.BudgetLinesList}}
+
+
+ {{$ReportBudgetLine.BudgetLineName}}
+ |
+
+ {{$ReportBudgetLine.Total}} kr
+ |
+
+ {{end}}
+
+ {{end}}
+
+ {{end}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/internal/web/urls.go b/internal/web/urls.go
index f422a06..ce639bd 100644
--- a/internal/web/urls.go
+++ b/internal/web/urls.go
@@ -5,23 +5,19 @@ import (
"embed"
"encoding/json"
"fmt"
- "github.com/datasektionen/GOrdian/internal/config"
- "github.com/datasektionen/GOrdian/internal/database"
- "github.com/datasektionen/GOrdian/internal/excel"
"html/template"
- "io"
"log/slog"
"math/rand"
"net/http"
"strconv"
+
+ "github.com/datasektionen/GOrdian/internal/config"
+ "github.com/datasektionen/GOrdian/internal/database"
)
-type FrameLine struct {
- FrameLineName string
- FrameLineIncome int
- FrameLineExpense int
- FrameLineInternal int
- FrameLineResult int
+type Databases struct {
+ DBCF *sql.DB
+ DBGO *sql.DB
}
const (
@@ -36,7 +32,7 @@ var staticFiles embed.FS
var templates *template.Template
-func Mount(mux *http.ServeMux, db *sql.DB) error {
+func Mount(mux *http.ServeMux, databases Databases) error {
var err error
tokenURL := config.GetEnv().LoginURL + "/login?callback=" + config.GetEnv().ServerURL + "/token?token="
templates, err = template.New("").Funcs(map[string]any{"formatMoney": formatMoney, "add": add, "sliceContains": sliceContains}).ParseFS(templatesFS, "templates/*.gohtml")
@@ -44,17 +40,18 @@ func Mount(mux *http.ServeMux, db *sql.DB) error {
return err
}
mux.Handle("/static/", http.FileServerFS(staticFiles))
- mux.Handle("/{$}", authRoute(db, indexPage, []string{}))
- mux.Handle("/costcentre/{costCentreIDPath}", authRoute(db, costCentrePage, []string{}))
+ mux.Handle("/{$}", authRoute(databases.DBGO, indexPage, []string{}))
+ mux.Handle("/costcentre/{costCentreIDPath}", authRoute(databases.DBGO, costCentrePage, []string{}))
mux.Handle("/login", http.RedirectHandler(tokenURL, http.StatusSeeOther))
- mux.Handle("/token", route(db, tokenPage))
- mux.Handle("/logout", route(db, logoutPage))
- mux.Handle("/admin", authRoute(db, adminPage, []string{"admin", "view-all"}))
- mux.Handle("/admin/upload", authRoute(db, uploadPage, []string{"admin"}))
- mux.Handle("/api/CostCentres", cors(route(db, apiCostCentres)))
- mux.Handle("/api/SecondaryCostCentres", cors(route(db, apiSecondaryCostCentre)))
- mux.Handle("/api/BudgetLines", cors(route(db, apiBudgetLine)))
- mux.Handle("/framebudget", authRoute(db, framePage, []string{}))
+ mux.Handle("/token", route(databases.DBGO, tokenPage))
+ mux.Handle("/logout", route(databases.DBGO, logoutPage))
+ mux.Handle("/admin", authRoute(databases.DBGO, adminPage, []string{"admin", "view-all"}))
+ mux.Handle("/admin/upload", authRoute(databases.DBGO, uploadPage, []string{"admin"}))
+ mux.Handle("/api/CostCentres", cors(route(databases.DBGO, apiCostCentres)))
+ mux.Handle("/api/SecondaryCostCentres", cors(route(databases.DBGO, apiSecondaryCostCentre)))
+ mux.Handle("/api/BudgetLines", cors(route(databases.DBGO, apiBudgetLine)))
+ mux.Handle("/framebudget", authRoute(databases.DBGO, framePage, []string{}))
+ mux.Handle("/resultreport", authRoute(databases.DBCF, reportPage, []string{}))
return nil
}
@@ -66,25 +63,6 @@ func cors(h http.Handler) http.Handler {
})
}
-func add(x int, y int) int {
- return x + y
-}
-
-func formatMoney(value int) string {
- numStr := strconv.Itoa(value)
- length := len(numStr)
- var result string
-
- for i := 0; i < length; i++ {
- if i > 0 && (length-i)%3 == 0 {
- result += " "
- }
- result += string(numStr[i])
- }
-
- return result
-}
-
func route(db *sql.DB, handler func(w http.ResponseWriter, r *http.Request, db *sql.DB) error) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := handler(w, r, db)
@@ -124,6 +102,9 @@ func authRoute(db *sql.DB, handler func(w http.ResponseWriter, r *http.Request,
return fmt.Errorf("failed to decode user body from json: %v", err)
}
userPerms, err := http.Get(config.GetEnv().PlsURL + "/api/user/" + loginBody.User + "/" + config.GetEnv().PlsSystem)
+ if err != nil {
+ return fmt.Errorf("no response from pls: %v", err)
+ }
var perms []string
err = json.NewDecoder(userPerms.Body).Decode(&perms)
@@ -154,45 +135,23 @@ func sliceContains(list1 []string, list2 ...string) bool {
return false
}
-func apiCostCentres(w http.ResponseWriter, r *http.Request, db *sql.DB) error {
- costCentres, err := getCostCentres(db)
- if err != nil {
- return fmt.Errorf("failed get scan cost centres information from database: %v", err)
- }
- err = json.NewEncoder(w).Encode(costCentres)
- if err != nil {
- return fmt.Errorf("failed to encode cost centres to json: %v", err)
- }
- return nil
+func add(x int, y int) int {
+ return x + y
}
-func apiSecondaryCostCentre(w http.ResponseWriter, r *http.Request, db *sql.DB) error {
- idCC, err := strconv.Atoi(r.FormValue("id"))
- if err != nil {
- return fmt.Errorf("failed to convert secondary cost centre id to int: %v", err)
- }
- secondaryCostCentres, err := getSecondaryCostCentresByCostCentreID(db, idCC)
- if err != nil {
- return fmt.Errorf("failed get scan sendondary cost centres information from database: %v", err)
- }
- err = json.NewEncoder(w).Encode(secondaryCostCentres)
- if err != nil {
- return fmt.Errorf("failed to encode secondary cost centres to json: %v", err)
- }
- return nil
-}
+func formatMoney(value int) string {
+ numStr := strconv.Itoa(value)
+ length := len(numStr)
+ var result string
-func apiBudgetLine(w http.ResponseWriter, r *http.Request, db *sql.DB) error {
- idSCC, err := strconv.Atoi(r.FormValue("id"))
- budgetLines, err := getBudgetLinesBySecondaryCostCentreID(db, idSCC)
- if err != nil {
- return fmt.Errorf("failed get scan budget lines information from database: %v", err)
- }
- err = json.NewEncoder(w).Encode(budgetLines)
- if err != nil {
- return fmt.Errorf("failed to encode budget lines to json: %v", err)
+ for i := 0; i < length; i++ {
+ if i > 0 && (length-i)%3 == 0 {
+ result += " "
+ }
+ result += string(numStr[i])
}
- return nil
+
+ return result
}
func adminPage(w http.ResponseWriter, r *http.Request, db *sql.DB, perms []string, loggedIn bool) error {
@@ -211,7 +170,7 @@ func uploadPage(w http.ResponseWriter, r *http.Request, db *sql.DB, perms []stri
if err != nil {
return fmt.Errorf("could not read file from form: %w", err)
}
- err = database.SaveBudget(file)
+ err = database.SaveBudget(file, db)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
@@ -234,461 +193,6 @@ func logoutPage(w http.ResponseWriter, r *http.Request, db *sql.DB) error {
return nil
}
-func indexPage(w http.ResponseWriter, r *http.Request, db *sql.DB, perms []string, loggedIn bool) error {
- costCentres, err := getCostCentres(db)
- if err != nil {
- return fmt.Errorf("failed get scan cost centre information from database: %v", err)
- }
- committeeCostCentres, projectCostCentres, otherCostCentres, err := splitCostCentresOnType(costCentres)
- if err != nil {
- return fmt.Errorf("failed to split cost centres on type: %v", err)
- }
-
- //Mörkläggning av mottagningens budget
- darkeningResp, err := http.Get("https://darkmode.datasektionen.se/")
- if err != nil {
- slog.Error("Failed to get status from darkmode", "error", err)
- return fmt.Errorf(": %v", err)
- }
- defer darkeningResp.Body.Close()
-
- if darkeningResp.StatusCode != http.StatusOK {
- slog.Error("Status error from darkmode", "error", darkeningResp.StatusCode)
- }
-
- darkeningBody, err := io.ReadAll(darkeningResp.Body)
- if err != nil {
- slog.Error("Failed to read body", "error", err)
- }
-
- darkeningValue, err := strconv.ParseBool(string(darkeningBody))
- if err != nil {
- slog.Error("Failed to parse bool", "error", err)
- }
-
- if darkeningValue {
- for index, committeeCostCentre := range committeeCostCentres {
- if committeeCostCentre.CostCentreName == "Mottagningen" {
- committeeCostCentres = append(committeeCostCentres[:index], committeeCostCentres[index+1:]...)
- break
- }
- }
- }
- //end of mörkläggning
-
- if err := templates.ExecuteTemplate(w, "index.gohtml", map[string]any{
- "motd": motdGenerator(),
- "committees": committeeCostCentres,
- "projects": projectCostCentres,
- "others": otherCostCentres,
- "permissions": perms,
- "loggedIn": loggedIn,
- }); err != nil {
- return fmt.Errorf("Could not render template: %w", err)
- }
- return nil
-}
-
-func framePage(w http.ResponseWriter, r *http.Request, db *sql.DB, perms []string, loggedIn bool) error {
- budgetLines, err := getFrameLines(db)
- if err != nil {
- return fmt.Errorf("failed get scan budget lines information from database: %v", err)
- }
- committeeFrameLines, projectFrameLines, otherFrameLines, totalFrameLine, sumCommitteeFrameLine, sumProjectFrameLine, sumOtherFrameLine, err := generateFrameLines(budgetLines)
- if err != nil {
- return fmt.Errorf("failed to generate frame budget lines: %v", err)
- }
- if err := templates.ExecuteTemplate(w, "frame.gohtml", map[string]any{
- "motd": motdGenerator(),
- "committeeframelines": committeeFrameLines,
- "projectframelines": projectFrameLines,
- "otherframelines": otherFrameLines,
- "totalframeline": totalFrameLine,
- "sumcommitteeframeline": sumCommitteeFrameLine,
- "sumprojectframeline": sumProjectFrameLine,
- "sumotherframeline": sumOtherFrameLine,
- "permissions": perms,
- "loggedIn": loggedIn,
- }); err != nil {
- return fmt.Errorf("Could not render template: %w", err)
- }
- return nil
-}
-
-func costCentrePage(w http.ResponseWriter, r *http.Request, db *sql.DB, perms []string, loggedIn bool) error {
- costCentreIDString := r.PathValue("costCentreIDPath")
- costCentreIDInt, err := strconv.Atoi(costCentreIDString)
- if err != nil {
- return fmt.Errorf("failed to convert cost centre id from string to int: %v", err)
- }
-
- budgetLines, err := getBudgetLinesByCostCentreID(db, costCentreIDInt)
- if err != nil {
- return fmt.Errorf("failed get scan budget line information from database: %v", err)
- }
-
- //omg
- secondaryCostCentresWithBudgetLinesList := make([]secondaryCostCentresWithBudgetLines, 1)
- currentSecondaryCostCentre := &secondaryCostCentresWithBudgetLinesList[0]
- for _, budgetLine := range budgetLines {
- if currentSecondaryCostCentre.SecondaryCostCentreName != budgetLine.SecondaryCostCentreName {
- secondaryCostCentresWithBudgetLinesList = append(secondaryCostCentresWithBudgetLinesList, secondaryCostCentresWithBudgetLines{
- SecondaryCostCentreName: budgetLine.SecondaryCostCentreName,
- BudgetLines: []excel.BudgetLine{},
- })
- currentSecondaryCostCentre = &secondaryCostCentresWithBudgetLinesList[len(secondaryCostCentresWithBudgetLinesList)-1]
- }
- currentSecondaryCostCentre.BudgetLines = append(currentSecondaryCostCentre.BudgetLines, budgetLine)
- }
- secondaryCostCentresWithBudgetLinesList = secondaryCostCentresWithBudgetLinesList[1:]
-
- costCentre, err := getCostCentreByID(db, costCentreIDInt)
- if err != nil {
- return fmt.Errorf("failed get scan cost centre information from database: %v", err)
- }
-
- //calc the total incomes, expenses and results of all cost centres in the list
- secondaryCostCentresWithBudgetLinesList, err = calculateSecondaryCostCentres(secondaryCostCentresWithBudgetLinesList)
- if err != nil {
- return fmt.Errorf("failed calculate secondary cost centre values: %v", err)
- }
-
- costCentreTotalIncome, costCentreTotalExpense, costCentreTotalResult, err := calculateCostCentre(secondaryCostCentresWithBudgetLinesList)
- if err != nil {
- return fmt.Errorf("failed calculate cost centre values: %v", err)
- }
-
- if err := templates.ExecuteTemplate(w, "costcentre.gohtml", map[string]any{
- "motd": motdGenerator(),
- "secondaryCostCentresWithBudgetLinesList": secondaryCostCentresWithBudgetLinesList,
- "costCentre": costCentre,
- "costCentreTotalIncome": costCentreTotalIncome,
- "costCentreTotalExpense": costCentreTotalExpense,
- "costCentreTotalResult": costCentreTotalResult,
- "permissions": perms,
- "loggedIn": loggedIn,
- }); err != nil {
- return fmt.Errorf("could not render template: %w", err)
- }
- return nil
-}
-
-func calculateCostCentre(secondaryCostCentresWithBudgetLinesList []secondaryCostCentresWithBudgetLines) (int, int, int, error) {
- var totalIncome int
- var totalExpense int
- for _, sCCWithBudgetLines := range secondaryCostCentresWithBudgetLinesList {
- totalIncome = totalIncome + sCCWithBudgetLines.SecondaryCostCentreTotalIncome
- totalExpense = totalExpense + sCCWithBudgetLines.SecondaryCostCentreTotalExpense
- }
- totalResult := totalIncome + totalExpense
-
- return totalIncome, totalExpense, totalResult, nil
-}
-
-func calculateSecondaryCostCentres(secondaryCostCentresWithBudgetLinesList []secondaryCostCentresWithBudgetLines) ([]secondaryCostCentresWithBudgetLines, error) {
- for index, sCCWithBudgetLines := range secondaryCostCentresWithBudgetLinesList {
- var totalIncome int
- var totalExpense int
- for _, budgetLine := range sCCWithBudgetLines.BudgetLines {
- totalIncome = totalIncome + budgetLine.BudgetLineIncome
- totalExpense = totalExpense + budgetLine.BudgetLineExpense
- }
- secondaryCostCentresWithBudgetLinesList[index].SecondaryCostCentreTotalIncome = totalIncome
- secondaryCostCentresWithBudgetLinesList[index].SecondaryCostCentreTotalExpense = totalExpense
- secondaryCostCentresWithBudgetLinesList[index].SecondaryCostCentreTotalResult = totalIncome + totalExpense
- }
- return secondaryCostCentresWithBudgetLinesList, nil
-}
-
-type secondaryCostCentresWithBudgetLines struct {
- SecondaryCostCentreName string
- SecondaryCostCentreTotalIncome int
- SecondaryCostCentreTotalExpense int
- SecondaryCostCentreTotalResult int
- BudgetLines []excel.BudgetLine
-}
-
-func getBudgetLinesByCostCentreID(db *sql.DB, costCentreID int) ([]excel.BudgetLine, error) {
- var budgetLinesGetStatementStatic = `
- SELECT
- budget_lines.id,
- budget_lines.name,
- income,
- expense,
- comment,
- account,
- secondary_cost_centres.id,
- secondary_cost_centres.name
- FROM budget_lines
- JOIN secondary_cost_centres ON secondary_cost_centres.id = secondary_cost_centre_id
- WHERE cost_centre_id = $1
- ORDER BY secondary_cost_centre_id
- `
- result, err := db.Query(budgetLinesGetStatementStatic, costCentreID)
- if err != nil {
- return nil, fmt.Errorf("failed to get budget lines from database: %v", err)
- }
- var budgetLines []excel.BudgetLine
- for result.Next() {
- var budgetLine excel.BudgetLine
-
- err := result.Scan(
- &budgetLine.BudgetLineID,
- &budgetLine.BudgetLineName,
- &budgetLine.BudgetLineIncome,
- &budgetLine.BudgetLineExpense,
- &budgetLine.BudgetLineComment,
- &budgetLine.BudgetLineAccount,
- &budgetLine.SecondaryCostCentreID,
- &budgetLine.SecondaryCostCentreName,
- )
- if err != nil {
- return nil, fmt.Errorf("failed to scan budget line from query result: %v", err)
- }
- budgetLines = append(budgetLines, budgetLine)
- }
- return budgetLines, nil
-}
-
-func getSecondaryCostCentresByCostCentreID(db *sql.DB, costCentreID int) ([]excel.SecondaryCostCentre, error) {
- var SecondaryCostCentresGetStatementStatic = `
- SELECT
- id,
- name,
- cost_centre_id
- FROM secondary_cost_centres
- WHERE cost_centre_id = $1
- ORDER BY id
- `
- result, err := db.Query(SecondaryCostCentresGetStatementStatic, costCentreID)
- if err != nil {
- return nil, fmt.Errorf("failed to get secondary cost centres from database: %v", err)
- }
- var secondaryCostCentres []excel.SecondaryCostCentre
- for result.Next() {
- var secondaryCostCentre excel.SecondaryCostCentre
-
- err := result.Scan(
- &secondaryCostCentre.SecondaryCostCentreID,
- &secondaryCostCentre.SecondaryCostCentreName,
- &secondaryCostCentre.CostCentreID,
- )
- if err != nil {
- return nil, fmt.Errorf("failed to scan secondary cost centre from query result: %v", err)
- }
- secondaryCostCentres = append(secondaryCostCentres, secondaryCostCentre)
- }
- return secondaryCostCentres, nil
-}
-
-func getBudgetLinesBySecondaryCostCentreID(db *sql.DB, secondaryCostCentreID int) ([]excel.BudgetLine, error) {
- var budgetLinesGetStatementStatic = `
- SELECT
- id,
- name,
- income,
- expense,
- comment,
- account,
- secondary_cost_centre_id
- FROM budget_lines
- WHERE secondary_cost_centre_id = $1
- ORDER BY id
- `
- result, err := db.Query(budgetLinesGetStatementStatic, secondaryCostCentreID)
- if err != nil {
- return nil, fmt.Errorf("failed to get budgetlines from database: %v", err)
- }
- var budgetLines []excel.BudgetLine
- for result.Next() {
- var budgetLine excel.BudgetLine
-
- err := result.Scan(
- &budgetLine.BudgetLineID,
- &budgetLine.BudgetLineName,
- &budgetLine.BudgetLineIncome,
- &budgetLine.BudgetLineExpense,
- &budgetLine.BudgetLineComment,
- &budgetLine.BudgetLineAccount,
- &budgetLine.SecondaryCostCentreID,
- )
- if err != nil {
- return nil, fmt.Errorf("failed to scan budget line from query result: %v", err)
- }
- budgetLines = append(budgetLines, budgetLine)
- }
- return budgetLines, nil
-}
-
-func getFrameLines(db *sql.DB) ([]excel.BudgetLine, error) {
- var frameLinesGetStatementStatic = `
- SELECT
- SUM(income),
- SUM(expense),
- secondary_cost_centres.name ILIKE '%Internt%',
- cost_centres.id,
- cost_centres.name,
- cost_centres.type
- FROM budget_lines
- JOIN secondary_cost_centres ON secondary_cost_centres.id = secondary_cost_centre_id
- JOIN cost_centres ON secondary_cost_centres.cost_centre_id = cost_centres.id
- GROUP BY cost_centres.id, cost_centres.name, cost_centres.type, secondary_cost_centres.name ILIKE '%Internt%'
- ORDER BY cost_centres.name, secondary_cost_centres.name ILIKE '%Internt%'
- `
- result, err := db.Query(frameLinesGetStatementStatic)
- if err != nil {
- return nil, fmt.Errorf("failed to get framelines from database: %v", err)
- }
- var frameLines []excel.BudgetLine
- for result.Next() {
- var frameLine excel.BudgetLine
-
- err := result.Scan(
- &frameLine.BudgetLineIncome,
- &frameLine.BudgetLineExpense,
- &frameLine.SecondaryCostCentreName,
- &frameLine.CostCentreID,
- &frameLine.CostCentreName,
- &frameLine.CostCentreType,
- )
- if err != nil {
- return nil, fmt.Errorf("failed to scan budget line from query result: %v", err)
- }
- frameLines = append(frameLines, frameLine)
- }
- return frameLines, nil
-}
-
-func getCostCentres(db *sql.DB) ([]excel.CostCentre, error) {
- var costCentresGetStatementStatic = `SELECT id, name, type FROM cost_centres ORDER BY name`
- result, err := db.Query(costCentresGetStatementStatic)
- if err != nil {
- return nil, fmt.Errorf("failed to get cost centres from database: %v", err)
- }
- var costCentres []excel.CostCentre
- for result.Next() {
- var costCentre excel.CostCentre
-
- err := result.Scan(&costCentre.CostCentreID, &costCentre.CostCentreName, &costCentre.CostCentreType)
- if err != nil {
- return nil, fmt.Errorf("failed to scan cost centre from query result: %v", err)
- }
- costCentres = append(costCentres, costCentre)
- }
- return costCentres, nil
-}
-
-func getCostCentreByID(db *sql.DB, costCentreID int) (excel.CostCentre, error) {
- var costCentreGetStatementStatic = `SELECT id, name, type FROM cost_centres WHERE id = $1`
- result := db.QueryRow(costCentreGetStatementStatic, costCentreID)
- var costCentre excel.CostCentre
- err := result.Scan(&costCentre.CostCentreID, &costCentre.CostCentreName, &costCentre.CostCentreType)
- if err != nil {
- return excel.CostCentre{}, fmt.Errorf("failed to scan cost centre from query result: %v", err)
- }
- return costCentre, nil
-}
-
-func splitCostCentresOnType(costCentres []excel.CostCentre) ([]excel.CostCentre, []excel.CostCentre, []excel.CostCentre, error) {
- var committeeCostCentres []excel.CostCentre
- var projectCostCentres []excel.CostCentre
- var otherCostCentres []excel.CostCentre
- for _, costCentre := range costCentres {
- switch costCentre.CostCentreType {
- case "committee":
- committeeCostCentres = append(committeeCostCentres, costCentre)
- case "project":
- projectCostCentres = append(projectCostCentres, costCentre)
- case "other":
- otherCostCentres = append(otherCostCentres, costCentre)
- default:
- return nil, nil, nil, fmt.Errorf("faulty cost centre type found when splitting")
- }
- }
- return committeeCostCentres, projectCostCentres, otherCostCentres, nil
-}
-
-func generateFrameLines(frameLines []excel.BudgetLine) ([]FrameLine, []FrameLine, []FrameLine, FrameLine, FrameLine, FrameLine, FrameLine, error) {
- var committeeFrameLines []FrameLine
- var projectFrameLines []FrameLine
- var otherFrameLines []FrameLine
- var totalFrameLine FrameLine
- var sumCommitteeFrameLine FrameLine
- var sumProjectFrameLine FrameLine
- var sumOtherFrameLine FrameLine
-
- totalFrameLine.FrameLineName = "Totalt"
- sumCommitteeFrameLine.FrameLineName = "Summa nämnder"
- sumProjectFrameLine.FrameLineName = "Summa projekt"
- sumOtherFrameLine.FrameLineName = "Summa övrigt"
-
- var skippidi bool
- for i, frameLine := range frameLines {
- if skippidi == true {
- skippidi = false
- continue
- }
- frameLineIncome := frameLine.BudgetLineIncome
- frameLineExpense := frameLine.BudgetLineExpense
- frameLineName := frameLine.CostCentreName
- frameLineInternal := 0
- frameLineResult := 0
-
- // each CC appears twice, once for internal costs, once for the rest
- // both are handled i and i+1
- // skippidi makes sure that the loop incements by two
- if i+1 < len(frameLines) && frameLines[i+1].CostCentreName == frameLineName {
- frameLineIncome += frameLines[i+1].BudgetLineIncome
- frameLineExpense += frameLines[i+1].BudgetLineExpense
- frameLineInternal = frameLines[i+1].BudgetLineIncome + frameLines[i+1].BudgetLineExpense
- skippidi = true
- }
-
- frameLineResult = frameLineIncome + frameLineExpense
-
- reconstructedFrameLine := FrameLine{frameLineName, frameLineIncome, frameLineExpense, frameLineInternal, frameLineResult}
-
- totalFrameLine.FrameLineIncome += frameLineIncome
- totalFrameLine.FrameLineExpense += frameLineExpense
- totalFrameLine.FrameLineInternal += frameLineInternal
- totalFrameLine.FrameLineResult += frameLineResult
-
- switch frameLine.CostCentreType {
- case "committee":
- committeeFrameLines = append(committeeFrameLines, reconstructedFrameLine)
- case "project":
- projectFrameLines = append(projectFrameLines, reconstructedFrameLine)
- case "other":
- otherFrameLines = append(otherFrameLines, reconstructedFrameLine)
- default:
- return nil, nil, nil, FrameLine{}, FrameLine{}, FrameLine{}, FrameLine{}, fmt.Errorf("faulty cost centre type found when splitting")
- }
- }
-
- for _, committeeFrameLine := range committeeFrameLines {
- sumCommitteeFrameLine.FrameLineIncome += committeeFrameLine.FrameLineIncome
- sumCommitteeFrameLine.FrameLineExpense += committeeFrameLine.FrameLineExpense
- sumCommitteeFrameLine.FrameLineInternal += committeeFrameLine.FrameLineInternal
- sumCommitteeFrameLine.FrameLineResult += committeeFrameLine.FrameLineResult
- }
-
- for _, ProjectFrameLine := range projectFrameLines {
- sumProjectFrameLine.FrameLineIncome += ProjectFrameLine.FrameLineIncome
- sumProjectFrameLine.FrameLineExpense += ProjectFrameLine.FrameLineExpense
- sumProjectFrameLine.FrameLineInternal += ProjectFrameLine.FrameLineInternal
- sumProjectFrameLine.FrameLineResult += ProjectFrameLine.FrameLineResult
- }
-
- for _, OtherFrameLine := range otherFrameLines {
- sumOtherFrameLine.FrameLineIncome += OtherFrameLine.FrameLineIncome
- sumOtherFrameLine.FrameLineExpense += OtherFrameLine.FrameLineExpense
- sumOtherFrameLine.FrameLineInternal += OtherFrameLine.FrameLineInternal
- sumOtherFrameLine.FrameLineResult += OtherFrameLine.FrameLineResult
- }
-
- return committeeFrameLines, projectFrameLines, otherFrameLines, totalFrameLine, sumCommitteeFrameLine, sumProjectFrameLine, sumOtherFrameLine, nil
-}
-
func motdGenerator() string {
options := []string{
"You have very many money:",