416 lines
15 KiB
Go
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)
|
|
}
|
|
}
|