Files
2026-05-31 16:07:30 +02:00

416 lines
15 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)
// Config
var (
couchdbURL = getEnv("COUCHDB_URL", "http://10.152.183.59:5984")
couchdbUser = getEnv("COUCHDB_USER", "admin")
couchdbPass = getEnv("COUCHDB_PASS", "Couchdb01")
dbName = getEnv("COUCHDB_DB", "aangiftes")
listenAddr = getEnv("LISTEN_ADDR", ":8080")
)
func getEnv(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
// Data structures
type CouchDate struct {
time.Time
}
func (c *CouchDate) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), `"`)
if s == "null" || s == "" {
return nil
}
t, err := time.Parse(time.RFC3339, s)
if err != nil {
return nil
}
c.Time = t
return nil
}
func (c CouchDate) MarshalJSON() ([]byte, error) {
if c.Time.IsZero() {
return []byte("null"), nil
}
return []byte(`"` + c.Time.Format(time.RFC3339) + `"`), nil
}
func (c CouchDate) String() string {
if c.Time.IsZero() {
return ""
}
return c.Time.Format("2006-01-02")
}
type Huisgenoot struct {
ID string `json:"_id,omitempty"`
Naam string `json:"naam"`
DatumVan CouchDate `json:"datumVan"`
DatumTotEnMet CouchDate `json:"datumTotEnMet"`
Samenlevingscontract bool `json:"samenlevingscontract"`
Pensioenregeling bool `json:"pensioenregeling"`
EigenaarWoning bool `json:"eigenaarwoning"`
MinderjarigKind bool `json:"minderjarigkind"`
FiscalePartner bool `json:"fiscalePartner"`
}
type Kind struct {
ID string `json:"_id,omitempty"`
Naam string `json:"naam"`
Geboortedatum CouchDate `json:"geboortedatum"`
Burgerservicenummer int64 `json:"burgerservicenummer"`
}
type InkomenLoondienst struct {
ID string `json:"_id,omitempty"`
Werkgever string `json:"werkgever"`
SoortInkomsten string `json:"soortInkomsten"`
Loon float64 `json:"loon"`
Loonheffing float64 `json:"loonheffing"`
Arbeidskorting float64 `json:"arbeidskorting"`
AndereInkomsten *float64 `json:"andereInkomsten"`
Reiskosten bool `json:"reiskosten"`
}
type PensioenUitkering struct {
ID string `json:"_id,omitempty"`
Land string `json:"land"`
Verzekeringsmaatschappij string `json:"verzekeringsmaatschappij"`
Loon float64 `json:"loon"`
Loonheffing float64 `json:"loonheffing"`
Kosten *float64 `json:"kosten"`
}
type Woning struct {
ID string `json:"_id,omitempty"`
Land string `json:"land"`
Postcode string `json:"postcode"`
Huisnummer int `json:"huisnummer"`
Woonplaats string `json:"woonplaats"`
Eigenaar bool `json:"eigenaar"`
AndereEigenaar bool `json:"andere_eigenaar"`
Eigendom bool `json:"eigendom"`
VeranderdEigendomsdeelLoopJaar bool `json:"veranderd_eigendomsdeel_loop_jaar"`
SituatieBeginJaar string `json:"situatie_begin_jaar"`
SituatieRestJaar bool `json:"situatie_rest_jaar"`
WozWaarde float64 `json:"woz_waarde"`
Verhuurd bool `json:"verhuurd"`
Erfpachtcanon bool `json:"erfpachtcanon"`
}
type Schuld struct {
ID string `json:"_id,omitempty"`
BankOfGeldverstrekker string `json:"bank_of_geldverstrekker"`
Nummer string `json:"nummer"`
SchuldVoorWoningJaNee string `json:"schuld_voor_woning_ja_nee"`
Jaar string `json:"jaar"`
Schuld0101 float64 `json:"schuld_0101"`
Schuld3112 float64 `json:"schuld_3112"`
Rente float64 `json:"rente"`
SchuldVanBeidenJaNee string `json:"schuld_van_beiden_ja_nee"`
LeningVoorWoningJaNee string `json:"lening_voor_woning_ja_nee"`
LeningGeheelVoorWoningJaNee string `json:"lening_geheel_voor_woning_ja_nee"`
}
type Aangifte struct {
ID string `json:"_id,omitempty"`
Rev string `json:"_rev,omitempty"`
LoginNaam string `json:"login_naam"`
Wachtwoord string `json:"wachtwoord"`
JaarAangifte int `json:"jaar_aangifte"`
JaarVoorJaarAangifte int `json:"jaar_voor_jaar_aangifte"`
Naam string `json:"naam"`
Geboortedatum CouchDate `json:"geboortedatum"`
Burgerservicenummer int64 `json:"burgerservicenummer"`
Telefoonnummer *string `json:"telefoonnummer"`
NummerBelastingconsulent *string `json:"nummer_belastingconsulent"`
Echtgenoot bool `json:"echtgenoot"`
Huisgenoot bool `json:"huisgenoot"`
SamenIngeschrevenJaNee string `json:"samen_ingeschreven_ja_nee"`
FiscalePartnerJaNee string `json:"fiscale_partner_ja_nee"`
AndereHuisgenootJaNee string `json:"andere_huisgenoot_ja_nee"`
NaamPartner string `json:"naam_partner"`
GeboortedatumPartner CouchDate `json:"geboortedatum_partner"`
BurgerservicenummerPartner int64 `json:"burgerservicenummer_partner"`
TelefoonnummerPartner *string `json:"telefoonnummer_partner"`
NummerBelastingconsulentPartner *string `json:"nummer_belastingconsulent_partner"`
Huisgenoten []Huisgenoot `json:"huisgenoten"`
Kinderen []Kind `json:"kinderen"`
InkomstenUitLoondienst []InkomenLoondienst `json:"inkomstenUitLoondienst"`
PensioenEnAndereUitkeringen []PensioenUitkering `json:"pensioenEnAndereUitkeringen"`
OndernemingJaNee string `json:"onderneming_ja_nee"`
OndernenerIBJaNee string `json:"ondernemer_ib_ja_nee"`
GeldverstrekkerJaNee string `json:"geldverstrekker_ja_nee"`
PartnerOndernenerIBJaNee string `json:"partner_ondernemer_ib_ja_nee"`
PartnerGeldverstrekkerJaNee string `json:"partner_geldverstrekker_ja_nee"`
Woningen []Woning `json:"woningen"`
SchuldenJaNee string `json:"schulden_ja_nee"`
Schulden []Schuld `json:"schulden"`
BankEnSpaarrekeningen bool `json:"bank_en_spaarrekeningen_ja_nee"`
GroeneSpaartegoeden bool `json:"groene_spaartegoeden_ja_nee"`
Beleggingen bool `json:"beleggingen_ja_nee"`
GroeneBeleggingen bool `json:"groene_beleggingen_ja_nee"`
AanmerkelijkBelang bool `json:"aanmerkelijk_belang_ja_nee"`
Kapitaalverzekeringen bool `json:"kapitaalverzekeringen_ja_nee"`
Bouwdepots bool `json:"bouwdepots_ja_nee"`
ContantGeld bool `json:"contant_geld_ja_nee"`
UitgeleendGeld bool `json:"uitgeleend_geld_ja_nee"`
RechtenOpPeriodiekUitkeringen bool `json:"rechten_op_periodieke_uitkeringen_ja_nee"`
OverigeBezittingen bool `json:"overige_bezittingen_ja_nee"`
WaardeHogerDanJaNee string `json:"waarde_meer_dan_ja_nee"`
LijfrenteJaNee bool `json:"lijfrente_ja_nee"`
InkomensvoorzieningenJaNee bool `json:"inkomensvoorzieningen_ja_nee"`
ZorgkostenJaNee bool `json:"zorgkosten_ja_nee"`
GiftenJaNee bool `json:"giften_ja_nee"`
StudiefinancieringJaNee bool `json:"studiefinanciering_ja_nee"`
AlimentatieJaNee bool `json:"alimentatie_ja_nee"`
GehandicapteJaNee bool `json:"gehandicapte_ja_nee"`
VoorJaarAangifteJaNee bool `json:"voor_jaar_aangifte_ja_nee"`
DatumIngediend *CouchDate `json:"datum_ingediend,omitempty"`
}
// mapCheckboxes returns the mapping of labels to struct field names
func mapCheckboxes(a *Aangifte) map[string]string {
return map[string]string{
"Bank- en spaarrekeningen": "bank_en_spaarrekeningen_ja_nee",
"Groene spaartegoeden": "groene_spaartegoeden_ja_nee",
"Beleggingen": "beleggingen_ja_nee",
"Groene beleggingen": "groene_beleggingen_ja_nee",
"Aanmerkelijk belang": "aanmerkelijk_belang_ja_nee",
"Kapitaalverzekeringen": "kapitaalverzekeringen_ja_nee",
"Bouwdepots": "bouwdepots_ja_nee",
"Contant geld": "contant_geld_ja_nee",
"Uitgeleend geld": "uitgeleend_geld_ja_nee",
"Rechten op periodieke uitkeringen": "rechten_op_periodieke_uitkeringen_ja_nee",
"Overige bezittingen": "overige_bezittingen_ja_nee",
"Lijfrente": "lijfrente_ja_nee",
"Inkomensvoorzieningen": "inkomensvoorzieningen_ja_nee",
"Zorgkosten": "zorgkosten_ja_nee",
"Giften": "giften_ja_nee",
"Studiefinanciering": "studiefinanciering_ja_nee",
"Alimentatie": "alimentatie_ja_nee",
"Gehandicapte": "gehandicapte_ja_nee",
"Voor jaar aangifte": "voor_jaar_aangifte_ja_nee",
}
}
// template setup with the fix
var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
"add": func(a, b int) int { return a + b },
"deref": func(p *string) string {
if p == nil {
return ""
}
return *p
},
"mapCheckboxes": mapCheckboxes, // 👈 register the function
}).Parse(allTemplates))
// handler for login page
func handleLogin(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
tmpl.ExecuteTemplate(w, "login", map[string]interface{}{})
return
}
bsn := strToInt64(r.FormValue("bsn"))
wachtwoord := r.FormValue("wachtwoord")
aangifte, err := findByBSN(bsn)
if err != nil {
tmpl.ExecuteTemplate(w, "login", map[string]interface{}{
"Error": "Fout bij ophalen gegevens.",
})
return
}
if aangifte != nil && aangifte.Wachtwoord != wachtwoord {
tmpl.ExecuteTemplate(w, "login", map[string]interface{}{
"Error": "Onjuist BSN of wachtwoord.",
})
return
}
if aangifte == nil {
aangifte = &Aangifte{
Burgerservicenummer: bsn,
Wachtwoord: wachtwoord,
JaarAangifte: time.Now().Year() - 1,
JaarVoorJaarAangifte: time.Now().Year() - 2,
}
}
sid := fmt.Sprintf("%d-%d", bsn, time.Now().UnixNano())
sessions[sid] = &Session{
BSN: bsn,
Aangifte: aangifte,
}
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: sid,
Path: "/",
})
http.Redirect(w, r, "/aangifte", http.StatusSeeOther)
}
// handleAangifte shows the main form page
func handleAangifte(w http.ResponseWriter, r *http.Request) {
sess, ok := getSession(r)
if !ok {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
tmpl.ExecuteTemplate(w, "aangifte", map[string]interface{}{
"Aangifte": sess.Aangifte,
})
}
// handleSave updates the Aangifte struct from POST form data
func handleSave(w http.ResponseWriter, r *http.Request) {
sess, ok := getSession(r)
if !ok {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Kon formulier niet parsen", http.StatusBadRequest)
return
}
// update all fields
checks := mapCheckboxes(sess.Aangifte)
for label, field := range checks {
val := r.FormValue(label)
b := val == "on"
setBoolField(sess.Aangifte, field, b)
}
// optional: update jaar fields if present
if jaar := r.FormValue("jaar_aangifte"); jaar != "" {
if y, err := strconv.Atoi(jaar); err == nil {
sess.Aangifte.JaarAangifte = y
}
}
if jaarVoor := r.FormValue("jaar_voor_jaar_aangifte"); jaarVoor != "" {
if y, err := strconv.Atoi(jaarVoor); err == nil {
sess.Aangifte.JaarVoorJaarAangifte = y
}
}
// store/save to your DB if needed; here we just update session
http.Redirect(w, r, "/aangifte", http.StatusSeeOther)
}
// handleLogout deletes the session and redirects to login
func handleLogout(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session")
if err == nil {
delete(sessions, cookie.Value)
cookie.MaxAge = -1
http.SetCookie(w, cookie)
}
http.Redirect(w, r, "/login", http.StatusSeeOther)
}
// helper to set boolean field via reflection
func setBoolField(a *Aangifte, fieldName string, val bool) {
v := reflect.ValueOf(a).Elem().FieldByName(fieldName)
if v.IsValid() && v.Kind() == reflect.Bool {
v.SetBool(val)
}
}
// getSession retrieves session from cookie
func getSession(r *http.Request) (*Session, bool) {
cookie, err := r.Cookie("session")
if err != nil {
return nil, false
}
sess, ok := sessions[cookie.Value]
return sess, ok
}
// allTemplates holds embedded templates (login + aangifte)
var allTemplates = `
{{define "login"}}
<!DOCTYPE html>
<html>
<head><title>Login</title></head>
<body>
<h1>Login</h1>
<form method="POST" action="/login">
<label>Username: <input type="text" name="username"></label><br>
<label>Password: <input type="password" name="password"></label><br>
<button type="submit">Login</button>
</form>
</body>
</html>
{{end}}
{{define "aangifte"}}
<!DOCTYPE html>
<html>
<head><title>Aangifte</title></head>
<body>
<h1>Aangifte Form</h1>
<form method="POST" action="/save">
<label>Jaar Aangifte: <input type="text" name="jaar_aangifte" value="{{.Aangifte.JaarAangifte}}"></label><br>
<label>Jaar Voor Jaar Aangifte: <input type="text" name="jaar_voor_jaar_aangifte" value="{{.Aangifte.JaarVoorJaarAangifte}}"></label><br>
{{range $i, $h := .Aangifte.Huisgenoten}}
<h3>Huisgenoot {{$i}}</h3>
<label>Naam: <input type="text" name="huisgenoot_{{$i}}_naam" value="{{$h.Naam}}"></label><br>
<label>Partner: <input type="checkbox" name="huisgenoot_{{$i}}_partner" {{if $h.Partner}}checked{{end}}></label><br>
<label>Aangifte: <input type="checkbox" name="huisgenoot_{{$i}}_aangifte" {{if $h.Aangifte}}checked{{end}}></label><br>
{{end}}
<button type="submit">Opslaan</button>
</form>
<br>
<a href="/logout">Logout</a>
</body>
</html>
{{end}}
`
func main() {
// parse templates
tmpl = template.Must(template.New("all").Parse(allTemplates))
// initialize sessions map
sessions = make(map[string]*Session)
// routes
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/aangifte", handleAangifte)
http.HandleFunc("/save", handleSave)
http.HandleFunc("/logout", handleLogout)
fmt.Println("Server gestart op :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}