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

1315 lines
56 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"`
}
// CouchDB helpers
func couchRequest(method, path string, body interface{}) (*http.Response, error) {
var bodyReader io.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(b)
}
req, err := http.NewRequest(method, couchdbURL+path, bodyReader)
if err != nil {
return nil, err
}
req.SetBasicAuth(couchdbUser, couchdbPass)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
return http.DefaultClient.Do(req)
}
func ensureDB() error {
resp, err := couchRequest("PUT", "/"+dbName, nil)
if err != nil {
return err
}
defer resp.Body.Close()
// 201 = created, 412 = already exists, both are fine
return nil
}
func ensureBSNIndex() error {
index := map[string]interface{}{
"index": map[string]interface{}{
"fields": []string{"burgerservicenummer"},
},
"name": "bsn-index",
"type": "json",
}
resp, err := couchRequest("POST", "/"+dbName+"/_index", index)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
func findByBSN(bsn int64) (*Aangifte, error) {
query := map[string]interface{}{
"selector": map[string]interface{}{
"burgerservicenummer": bsn,
},
"limit": 1,
}
resp, err := couchRequest("POST", "/"+dbName+"/_find", query)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Docs []Aangifte `json:"docs"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if len(result.Docs) == 0 {
return nil, nil
}
return &result.Docs[0], nil
}
func saveAangifte(a *Aangifte) error {
var resp *http.Response
var err error
if a.ID == "" {
resp, err = couchRequest("POST", "/"+dbName, a)
} else {
resp, err = couchRequest("PUT", "/"+dbName+"/"+a.ID, a)
}
if err != nil {
return err
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if id, ok := result["id"].(string); ok {
a.ID = id
}
if rev, ok := result["rev"].(string); ok {
a.Rev = rev
}
return nil
}
// Session store (simple in-memory)
type Session struct {
BSN int64
Aangifte *Aangifte
}
var sessions = map[string]*Session{}
func sessionID(r *http.Request) string {
c, err := r.Cookie("session")
if err != nil {
return ""
}
return c.Value
}
func getSession(r *http.Request) *Session {
id := sessionID(r)
if id == "" {
return nil
}
return sessions[id]
}
// Templates
var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
"deref": func(p *string) string {
if p == nil {
return ""
}
return *p
},
}).Parse(allTemplates))
const allTemplates = `
{{define "login"}}
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Aangifte Inkomstenbelasting</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background: #1a1a2e; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.login-card { background: #16213e; border: 1px solid #0f3460; border-radius: 12px; padding: 2.5rem; width: 100%; max-width: 420px; box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
h1 { color: #e94560; font-size: 1.4rem; font-weight: 700; margin-bottom: 0.25rem; }
.subtitle { color: #a8b2d8; font-size: 0.85rem; margin-bottom: 2rem; }
label { color: #a8b2d8; font-size: 0.85rem; }
.form-control { background: #0f3460; border: 1px solid #1a4a7a; color: #e0e0e0; }
.form-control:focus { background: #0f3460; border-color: #e94560; color: #e0e0e0; box-shadow: 0 0 0 0.2rem rgba(233,69,96,0.25); }
.btn-primary { background: #e94560; border: none; width: 100%; padding: 0.75rem; font-weight: 600; }
.btn-primary:hover { background: #c73652; }
.error { color: #e94560; font-size: 0.85rem; margin-top: 1rem; }
</style>
</head>
<body>
<div class="login-card">
<h1>Aangifte Inkomstenbelasting</h1>
<p class="subtitle">Log in met uw BSN en wachtwoord</p>
{{if .Error}}<div class="error">{{.Error}}</div>{{end}}
<form method="POST" action="/login">
<div class="mb-3">
<label>Burgerservicenummer</label>
<input type="number" name="bsn" class="form-control mt-1" required>
</div>
<div class="mb-4">
<label>Wachtwoord</label>
<input type="password" name="wachtwoord" class="form-control mt-1" required>
</div>
<button type="submit" class="btn btn-primary">Inloggen</button>
</form>
</div>
</body>
</html>
{{end}}
{{define "aangifte"}}
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Aangifte - {{.Naam}}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style>
body { background: #f0f2f5; }
.navbar { background: #1a1a2e !important; }
.navbar-brand { color: #e94560 !important; font-weight: 700; }
.nav-link { color: #a8b2d8 !important; }
.section-card { background: white; border-radius: 10px; padding: 1.5rem; margin-bottom: 1.5rem; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.section-title { font-size: 1rem; font-weight: 700; color: #1a1a2e; border-bottom: 2px solid #e94560; padding-bottom: 0.5rem; margin-bottom: 1.25rem; }
.array-item { background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 1rem; margin-bottom: 0.75rem; position: relative; }
.btn-remove { position: absolute; top: 0.5rem; right: 0.5rem; }
.btn-add { border: 2px dashed #e94560; color: #e94560; background: transparent; border-radius: 8px; padding: 0.5rem 1rem; width: 100%; margin-top: 0.5rem; }
.btn-add:hover { background: #fff0f3; }
.btn-save { background: #e94560; border: none; padding: 0.75rem 2.5rem; font-weight: 600; border-radius: 8px; }
.btn-save:hover { background: #c73652; }
label { font-size: 0.82rem; color: #666; margin-bottom: 0.2rem; }
.form-control, .form-select { font-size: 0.9rem; }
.success-toast { position: fixed; bottom: 2rem; right: 2rem; background: #198754; color: white; padding: 1rem 1.5rem; border-radius: 8px; display: none; z-index: 9999; }
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg mb-4">
<div class="container">
<span class="navbar-brand">Aangifte Inkomstenbelasting {{.JaarAangifte}}</span>
<div class="ms-auto">
<span class="nav-link d-inline">{{.Naam}}</span>
<a href="/logout" class="btn btn-sm btn-outline-danger ms-3">Uitloggen</a>
</div>
</div>
</nav>
<div class="container pb-5">
<form method="POST" action="/save" id="mainForm">
{{if .ID}}<input type="hidden" name="_id" value="{{.ID}}">{{end}}
{{if .Rev}}<input type="hidden" name="_rev" value="{{.Rev}}">{{end}}
<!-- Persoonlijke gegevens -->
<div class="section-card">
<div class="section-title">Persoonlijke gegevens</div>
<div class="row g-3">
<div class="col-md-4">
<label>Naam</label>
<input type="text" name="naam" class="form-control" value="{{.Naam}}">
</div>
<div class="col-md-3">
<label>Geboortedatum</label>
<input type="date" name="geboortedatum" class="form-control" value="{{.Geboortedatum}}">
</div>
<div class="col-md-3">
<label>BSN</label>
<input type="number" name="burgerservicenummer" class="form-control" value="{{.Burgerservicenummer}}">
</div>
<div class="col-md-2">
<label>Jaar aangifte</label>
<input type="number" name="jaar_aangifte" class="form-control" value="{{.JaarAangifte}}">
</div>
<div class="col-md-4">
<label>Telefoonnummer</label>
<input type="text" name="telefoonnummer" class="form-control" value="{{deref .Telefoonnummer}}">
</div>
<div class="col-md-4">
<label>Nummer belastingconsulent</label>
<input type="text" name="nummer_belastingconsulent" class="form-control" value="{{deref .NummerBelastingconsulent}}">
</div>
<div class="col-md-2">
<label>Login naam</label>
<input type="text" name="login_naam" class="form-control" value="{{.LoginNaam}}">
</div>
<div class="col-md-2">
<label>Wachtwoord</label>
<input type="password" name="wachtwoord" class="form-control" value="{{.Wachtwoord}}">
</div>
</div>
</div>
<!-- Partner -->
<div class="section-card">
<div class="section-title">Partner / Fiscale partner</div>
<div class="row g-3">
<div class="col-md-3">
<label>Echtgenoot</label>
<select name="echtgenoot" class="form-select">
<option value="true" {{if .Echtgenoot}}selected{{end}}>Ja</option>
<option value="false" {{if not .Echtgenoot}}selected{{end}}>Nee</option>
</select>
</div>
<div class="col-md-3">
<label>Huisgenoot</label>
<select name="huisgenoot" class="form-select">
<option value="true" {{if .Huisgenoot}}selected{{end}}>Ja</option>
<option value="false" {{if not .Huisgenoot}}selected{{end}}>Nee</option>
</select>
</div>
<div class="col-md-3">
<label>Samen ingeschreven</label>
<select name="samen_ingeschreven_ja_nee" class="form-select">
<option value="ja" {{if eq .SamenIngeschrevenJaNee "ja"}}selected{{end}}>Ja</option>
<option value="nee" {{if eq .SamenIngeschrevenJaNee "nee"}}selected{{end}}>Nee</option>
</select>
</div>
<div class="col-md-3">
<label>Fiscale partner</label>
<select name="fiscale_partner_ja_nee" class="form-select">
<option value="ja" {{if eq .FiscalePartnerJaNee "ja"}}selected{{end}}>Ja</option>
<option value="nee" {{if eq .FiscalePartnerJaNee "nee"}}selected{{end}}>Nee</option>
</select>
</div>
<div class="col-md-4">
<label>Naam partner</label>
<input type="text" name="naam_partner" class="form-control" value="{{.NaamPartner}}">
</div>
<div class="col-md-3">
<label>Geboortedatum partner</label>
<input type="date" name="geboortedatum_partner" class="form-control" value="{{.GeboortedatumPartner}}">
</div>
<div class="col-md-3">
<label>BSN partner</label>
<input type="number" name="burgerservicenummer_partner" class="form-control" value="{{.BurgerservicenummerPartner}}">
</div>
</div>
</div>
<!-- Huisgenoten -->
<div class="section-card">
<div class="section-title">Huisgenoten</div>
<div id="huisgenoten-list">
{{range $i, $h := .Huisgenoten}}
<div class="array-item" id="huisgenoot-{{$i}}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('huisgenoot-{{$i}}')">✕</button>
<div class="row g-2">
<div class="col-md-4">
<label>Naam</label>
<input type="text" name="huisgenoten[{{$i}}][naam]" class="form-control" value="{{$h.Naam}}">
</div>
<div class="col-md-3">
<label>Datum van</label>
<input type="date" name="huisgenoten[{{$i}}][datumVan]" class="form-control" value="{{$h.DatumVan}}">
</div>
<div class="col-md-3">
<label>Datum tot en met</label>
<input type="date" name="huisgenoten[{{$i}}][datumTotEnMet]" class="form-control" value="{{$h.DatumTotEnMet}}">
</div>
<div class="col-md-2">
<label>Fiscale partner</label>
<select name="huisgenoten[{{$i}}][fiscalePartner]" class="form-select">
<option value="true" {{if $h.FiscalePartner}}selected{{end}}>Ja</option>
<option value="false" {{if not $h.FiscalePartner}}selected{{end}}>Nee</option>
</select>
</div>
<div class="col-md-2">
<label>Samenlevingscontract</label>
<select name="huisgenoten[{{$i}}][samenlevingscontract]" class="form-select">
<option value="true" {{if $h.Samenlevingscontract}}selected{{end}}>Ja</option>
<option value="false">Nee</option>
</select>
</div>
<div class="col-md-2">
<label>Pensioenregeling</label>
<select name="huisgenoten[{{$i}}][pensioenregeling]" class="form-select">
<option value="true" {{if $h.Pensioenregeling}}selected{{end}}>Ja</option>
<option value="false">Nee</option>
</select>
</div>
<div class="col-md-2">
<label>Eigenaar woning</label>
<select name="huisgenoten[{{$i}}][eigenaarwoning]" class="form-select">
<option value="true" {{if $h.EigenaarWoning}}selected{{end}}>Ja</option>
<option value="false">Nee</option>
</select>
</div>
<div class="col-md-2">
<label>Minderjarig kind</label>
<select name="huisgenoten[{{$i}}][minderjarigkind]" class="form-select">
<option value="true" {{if $h.MinderjarigKind}}selected{{end}}>Ja</option>
<option value="false">Nee</option>
</select>
</div>
</div>
</div>
{{end}}
</div>
<button type="button" class="btn-add" onclick="addHuisgenoot()">+ Huisgenoot toevoegen</button>
</div>
<!-- Kinderen -->
<div class="section-card">
<div class="section-title">Kinderen</div>
<div id="kinderen-list">
{{range $i, $k := .Kinderen}}
<div class="array-item" id="kind-{{$i}}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('kind-{{$i}}')">✕</button>
<div class="row g-2">
<div class="col-md-4">
<label>Naam</label>
<input type="text" name="kinderen[{{$i}}][naam]" class="form-control" value="{{$k.Naam}}">
</div>
<div class="col-md-3">
<label>Geboortedatum</label>
<input type="date" name="kinderen[{{$i}}][geboortedatum]" class="form-control" value="{{$k.Geboortedatum}}">
</div>
<div class="col-md-3">
<label>BSN</label>
<input type="number" name="kinderen[{{$i}}][burgerservicenummer]" class="form-control" value="{{$k.Burgerservicenummer}}">
</div>
</div>
</div>
{{end}}
</div>
<button type="button" class="btn-add" onclick="addKind()">+ Kind toevoegen</button>
</div>
<!-- Inkomsten uit loondienst -->
<div class="section-card">
<div class="section-title">Inkomsten uit loondienst</div>
<div id="loondienst-list">
{{range $i, $l := .InkomstenUitLoondienst}}
<div class="array-item" id="loondienst-{{$i}}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('loondienst-{{$i}}')">✕</button>
<div class="row g-2">
<div class="col-md-4">
<label>Werkgever</label>
<input type="text" name="loondienst[{{$i}}][werkgever]" class="form-control" value="{{$l.Werkgever}}">
</div>
<div class="col-md-3">
<label>Soort inkomsten</label>
<input type="text" name="loondienst[{{$i}}][soortInkomsten]" class="form-control" value="{{$l.SoortInkomsten}}">
</div>
<div class="col-md-2">
<label>Loon</label>
<input type="number" step="0.01" name="loondienst[{{$i}}][loon]" class="form-control" value="{{$l.Loon}}">
</div>
<div class="col-md-2">
<label>Loonheffing</label>
<input type="number" step="0.01" name="loondienst[{{$i}}][loonheffing]" class="form-control" value="{{$l.Loonheffing}}">
</div>
<div class="col-md-2">
<label>Arbeidskorting</label>
<input type="number" step="0.01" name="loondienst[{{$i}}][arbeidskorting]" class="form-control" value="{{$l.Arbeidskorting}}">
</div>
<div class="col-md-2">
<label>Reiskosten</label>
<select name="loondienst[{{$i}}][reiskosten]" class="form-select">
<option value="true" {{if $l.Reiskosten}}selected{{end}}>Ja</option>
<option value="false">Nee</option>
</select>
</div>
</div>
</div>
{{end}}
</div>
<button type="button" class="btn-add" onclick="addLoondienst()">+ Inkomen toevoegen</button>
</div>
<!-- Pensioen en andere uitkeringen -->
<div class="section-card">
<div class="section-title">Pensioen en andere uitkeringen</div>
<div id="pensioen-list">
{{range $i, $p := .PensioenEnAndereUitkeringen}}
<div class="array-item" id="pensioen-{{$i}}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('pensioen-{{$i}}')">✕</button>
<div class="row g-2">
<div class="col-md-2">
<label>Land</label>
<input type="text" name="pensioen[{{$i}}][land]" class="form-control" value="{{$p.Land}}">
</div>
<div class="col-md-4">
<label>Verzekeringsmaatschappij</label>
<input type="text" name="pensioen[{{$i}}][verzekeringsmaatschappij]" class="form-control" value="{{$p.Verzekeringsmaatschappij}}">
</div>
<div class="col-md-2">
<label>Loon</label>
<input type="number" step="0.01" name="pensioen[{{$i}}][loon]" class="form-control" value="{{$p.Loon}}">
</div>
<div class="col-md-2">
<label>Loonheffing</label>
<input type="number" step="0.01" name="pensioen[{{$i}}][loonheffing]" class="form-control" value="{{$p.Loonheffing}}">
</div>
</div>
</div>
{{end}}
</div>
<button type="button" class="btn-add" onclick="addPensioen()">+ Uitkering toevoegen</button>
</div>
<!-- Woningen -->
<div class="section-card">
<div class="section-title">Woningen</div>
<div id="woningen-list">
{{range $i, $w := .Woningen}}
<div class="array-item" id="woning-{{$i}}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('woning-{{$i}}')">✕</button>
<div class="row g-2">
<div class="col-md-2">
<label>Land</label>
<input type="text" name="woningen[{{$i}}][land]" class="form-control" value="{{$w.Land}}">
</div>
<div class="col-md-2">
<label>Postcode</label>
<input type="text" name="woningen[{{$i}}][postcode]" class="form-control" value="{{$w.Postcode}}">
</div>
<div class="col-md-1">
<label>Huisnummer</label>
<input type="number" name="woningen[{{$i}}][huisnummer]" class="form-control" value="{{$w.Huisnummer}}">
</div>
<div class="col-md-3">
<label>Woonplaats</label>
<input type="text" name="woningen[{{$i}}][woonplaats]" class="form-control" value="{{$w.Woonplaats}}">
</div>
<div class="col-md-2">
<label>WOZ waarde</label>
<input type="number" step="0.01" name="woningen[{{$i}}][woz_waarde]" class="form-control" value="{{$w.WozWaarde}}">
</div>
<div class="col-md-2">
<label>Situatie begin jaar</label>
<input type="text" name="woningen[{{$i}}][situatie_begin_jaar]" class="form-control" value="{{$w.SituatieBeginJaar}}">
</div>
<div class="col-md-2">
<label>Eigenaar</label>
<select name="woningen[{{$i}}][eigenaar]" class="form-select">
<option value="true" {{if $w.Eigenaar}}selected{{end}}>Ja</option>
<option value="false">Nee</option>
</select>
</div>
<div class="col-md-2">
<label>Verhuurd</label>
<select name="woningen[{{$i}}][verhuurd]" class="form-select">
<option value="true" {{if $w.Verhuurd}}selected{{end}}>Ja</option>
<option value="false">Nee</option>
</select>
</div>
<div class="col-md-2">
<label>Erfpachtcanon</label>
<select name="woningen[{{$i}}][erfpachtcanon]" class="form-select">
<option value="true" {{if $w.Erfpachtcanon}}selected{{end}}>Ja</option>
<option value="false">Nee</option>
</select>
</div>
</div>
</div>
{{end}}
</div>
<button type="button" class="btn-add" onclick="addWoning()">+ Woning toevoegen</button>
</div>
<!-- Schulden -->
<div class="section-card">
<div class="section-title">Schulden / Hypotheken</div>
<div id="schulden-list">
{{range $i, $s := .Schulden}}
<div class="array-item" id="schuld-{{$i}}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('schuld-{{$i}}')">✕</button>
<div class="row g-2">
<div class="col-md-3">
<label>Bank / Geldverstrekker</label>
<input type="text" name="schulden[{{$i}}][bank_of_geldverstrekker]" class="form-control" value="{{$s.BankOfGeldverstrekker}}">
</div>
<div class="col-md-2">
<label>Nummer</label>
<input type="text" name="schulden[{{$i}}][nummer]" class="form-control" value="{{$s.Nummer}}">
</div>
<div class="col-md-1">
<label>Jaar</label>
<input type="text" name="schulden[{{$i}}][jaar]" class="form-control" value="{{$s.Jaar}}">
</div>
<div class="col-md-2">
<label>Schuld 01-01</label>
<input type="number" step="0.01" name="schulden[{{$i}}][schuld_0101]" class="form-control" value="{{$s.Schuld0101}}">
</div>
<div class="col-md-2">
<label>Schuld 31-12</label>
<input type="number" step="0.01" name="schulden[{{$i}}][schuld_3112]" class="form-control" value="{{$s.Schuld3112}}">
</div>
<div class="col-md-2">
<label>Rente</label>
<input type="number" step="0.01" name="schulden[{{$i}}][rente]" class="form-control" value="{{$s.Rente}}">
</div>
<div class="col-md-2">
<label>Schuld voor woning</label>
<select name="schulden[{{$i}}][schuld_voor_woning_ja_nee]" class="form-select">
<option value="ja" {{if eq $s.SchuldVoorWoningJaNee "ja"}}selected{{end}}>Ja</option>
<option value="nee">Nee</option>
</select>
</div>
<div class="col-md-2">
<label>Schuld van beiden</label>
<select name="schulden[{{$i}}][schuld_van_beiden_ja_nee]" class="form-select">
<option value="ja" {{if eq $s.SchuldVanBeidenJaNee "ja"}}selected{{end}}>Ja</option>
<option value="nee">Nee</option>
</select>
</div>
</div>
</div>
{{end}}
</div>
<button type="button" class="btn-add" onclick="addSchuld()">+ Schuld toevoegen</button>
</div>
<!-- Bezittingen en vinkjes -->
<div class="section-card">
<div class="section-title">Bezittingen en overige gegevens</div>
<div class="row g-3">
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="bank_en_spaarrekeningen_ja_nee" id="cb1" {{if .BankEnSpaarrekeningen}}checked{{end}}>
<label class="form-check-label" for="cb1">Bank- en spaarrekeningen</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="groene_spaartegoeden_ja_nee" id="cb2" {{if .GroeneSpaartegoeden}}checked{{end}}>
<label class="form-check-label" for="cb2">Groene spaartegoeden</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="beleggingen_ja_nee" id="cb3" {{if .Beleggingen}}checked{{end}}>
<label class="form-check-label" for="cb3">Beleggingen</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="groene_beleggingen_ja_nee" id="cb4" {{if .GroeneBeleggingen}}checked{{end}}>
<label class="form-check-label" for="cb4">Groene beleggingen</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="aanmerkelijk_belang_ja_nee" id="cb5" {{if .AanmerkelijkBelang}}checked{{end}}>
<label class="form-check-label" for="cb5">Aanmerkelijk belang</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="kapitaalverzekeringen_ja_nee" id="cb6" {{if .Kapitaalverzekeringen}}checked{{end}}>
<label class="form-check-label" for="cb6">Kapitaalverzekeringen</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="bouwdepots_ja_nee" id="cb7" {{if .Bouwdepots}}checked{{end}}>
<label class="form-check-label" for="cb7">Bouwdepots</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="contant_geld_ja_nee" id="cb8" {{if .ContantGeld}}checked{{end}}>
<label class="form-check-label" for="cb8">Contant geld</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="uitgeleend_geld_ja_nee" id="cb9" {{if .UitgeleendGeld}}checked{{end}}>
<label class="form-check-label" for="cb9">Uitgeleend geld</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="rechten_op_periodieke_uitkeringen_ja_nee" id="cb10" {{if .RechtenOpPeriodiekUitkeringen}}checked{{end}}>
<label class="form-check-label" for="cb10">Rechten op periodieke uitkeringen</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="overige_bezittingen_ja_nee" id="cb11" {{if .OverigeBezittingen}}checked{{end}}>
<label class="form-check-label" for="cb11">Overige bezittingen</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="lijfrente_ja_nee" id="cb12" {{if .LijfrenteJaNee}}checked{{end}}>
<label class="form-check-label" for="cb12">Lijfrente</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="inkomensvoorzieningen_ja_nee" id="cb13" {{if .InkomensvoorzieningenJaNee}}checked{{end}}>
<label class="form-check-label" for="cb13">Inkomensvoorzieningen</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="zorgkosten_ja_nee" id="cb14" {{if .ZorgkostenJaNee}}checked{{end}}>
<label class="form-check-label" for="cb14">Zorgkosten</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="giften_ja_nee" id="cb15" {{if .GiftenJaNee}}checked{{end}}>
<label class="form-check-label" for="cb15">Giften</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="studiefinanciering_ja_nee" id="cb16" {{if .StudiefinancieringJaNee}}checked{{end}}>
<label class="form-check-label" for="cb16">Studiefinanciering</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="alimentatie_ja_nee" id="cb17" {{if .AlimentatieJaNee}}checked{{end}}>
<label class="form-check-label" for="cb17">Alimentatie</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="gehandicapte_ja_nee" id="cb18" {{if .GehandicapteJaNee}}checked{{end}}>
<label class="form-check-label" for="cb18">Gehandicapte</label>
</div></div>
<div class="col-md-3"><div class="form-check">
<input class="form-check-input" type="checkbox" name="voor_jaar_aangifte_ja_nee" id="cb19" {{if .VoorJaarAangifteJaNee}}checked{{end}}>
<label class="form-check-label" for="cb19">Voor jaar aangifte</label>
</div></div>
</div>
</div>
<div class="d-flex justify-content-end mt-3">
<button type="submit" class="btn btn-primary btn-save">Opslaan</button>
</div>
</form>
</div>
<div class="success-toast" id="toast">✓ Opgeslagen!</div>
<script>
let huisgenotenCount = {{len .Huisgenoten}};
let kinderenCount = {{len .Kinderen}};
let loondienstCount = {{len .InkomstenUitLoondienst}};
let pensioenCount = {{len .PensioenEnAndereUitkeringen}};
let woningenCount = {{len .Woningen}};
let schuldenCount = {{len .Schulden}};
function removeItem(id) {
document.getElementById(id).remove();
}
function addHuisgenoot() {
const i = huisgenotenCount++;
document.getElementById('huisgenoten-list').insertAdjacentHTML('beforeend', ` + "`" + `
<div class="array-item" id="huisgenoot-${i}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('huisgenoot-${i}')">✕</button>
<div class="row g-2">
<div class="col-md-4"><label>Naam</label><input type="text" name="huisgenoten[${i}][naam]" class="form-control"></div>
<div class="col-md-3"><label>Datum van</label><input type="date" name="huisgenoten[${i}][datumVan]" class="form-control"></div>
<div class="col-md-3"><label>Datum tot en met</label><input type="date" name="huisgenoten[${i}][datumTotEnMet]" class="form-control"></div>
<div class="col-md-2"><label>Fiscale partner</label><select name="huisgenoten[${i}][fiscalePartner]" class="form-select"><option value="true">Ja</option><option value="false">Nee</option></select></div>
<div class="col-md-2"><label>Samenlevingscontract</label><select name="huisgenoten[${i}][samenlevingscontract]" class="form-select"><option value="true">Ja</option><option value="false">Nee</option></select></div>
<div class="col-md-2"><label>Pensioenregeling</label><select name="huisgenoten[${i}][pensioenregeling]" class="form-select"><option value="true">Ja</option><option value="false">Nee</option></select></div>
<div class="col-md-2"><label>Eigenaar woning</label><select name="huisgenoten[${i}][eigenaarwoning]" class="form-select"><option value="true">Ja</option><option value="false">Nee</option></select></div>
<div class="col-md-2"><label>Minderjarig kind</label><select name="huisgenoten[${i}][minderjarigkind]" class="form-select"><option value="true">Ja</option><option value="false">Nee</option></select></div>
</div>
</div>` + "`" + `);
}
function addKind() {
const i = kinderenCount++;
document.getElementById('kinderen-list').insertAdjacentHTML('beforeend', ` + "`" + `
<div class="array-item" id="kind-${i}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('kind-${i}')">✕</button>
<div class="row g-2">
<div class="col-md-4"><label>Naam</label><input type="text" name="kinderen[${i}][naam]" class="form-control"></div>
<div class="col-md-3"><label>Geboortedatum</label><input type="date" name="kinderen[${i}][geboortedatum]" class="form-control"></div>
<div class="col-md-3"><label>BSN</label><input type="number" name="kinderen[${i}][burgerservicenummer]" class="form-control"></div>
</div>
</div>` + "`" + `);
}
function addLoondienst() {
const i = loondienstCount++;
document.getElementById('loondienst-list').insertAdjacentHTML('beforeend', ` + "`" + `
<div class="array-item" id="loondienst-${i}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('loondienst-${i}')">✕</button>
<div class="row g-2">
<div class="col-md-4"><label>Werkgever</label><input type="text" name="loondienst[${i}][werkgever]" class="form-control"></div>
<div class="col-md-3"><label>Soort inkomsten</label><input type="text" name="loondienst[${i}][soortInkomsten]" class="form-control"></div>
<div class="col-md-2"><label>Loon</label><input type="number" step="0.01" name="loondienst[${i}][loon]" class="form-control"></div>
<div class="col-md-2"><label>Loonheffing</label><input type="number" step="0.01" name="loondienst[${i}][loonheffing]" class="form-control"></div>
<div class="col-md-2"><label>Arbeidskorting</label><input type="number" step="0.01" name="loondienst[${i}][arbeidskorting]" class="form-control"></div>
<div class="col-md-2"><label>Reiskosten</label><select name="loondienst[${i}][reiskosten]" class="form-select"><option value="true">Ja</option><option value="false">Nee</option></select></div>
</div>
</div>` + "`" + `);
}
function addPensioen() {
const i = pensioenCount++;
document.getElementById('pensioen-list').insertAdjacentHTML('beforeend', ` + "`" + `
<div class="array-item" id="pensioen-${i}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('pensioen-${i}')">✕</button>
<div class="row g-2">
<div class="col-md-2"><label>Land</label><input type="text" name="pensioen[${i}][land]" class="form-control"></div>
<div class="col-md-4"><label>Verzekeringsmaatschappij</label><input type="text" name="pensioen[${i}][verzekeringsmaatschappij]" class="form-control"></div>
<div class="col-md-2"><label>Loon</label><input type="number" step="0.01" name="pensioen[${i}][loon]" class="form-control"></div>
<div class="col-md-2"><label>Loonheffing</label><input type="number" step="0.01" name="pensioen[${i}][loonheffing]" class="form-control"></div>
</div>
</div>` + "`" + `);
}
function addWoning() {
const i = woningenCount++;
document.getElementById('woningen-list').insertAdjacentHTML('beforeend', ` + "`" + `
<div class="array-item" id="woning-${i}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('woning-${i}')">✕</button>
<div class="row g-2">
<div class="col-md-2"><label>Land</label><input type="text" name="woningen[${i}][land]" class="form-control"></div>
<div class="col-md-2"><label>Postcode</label><input type="text" name="woningen[${i}][postcode]" class="form-control"></div>
<div class="col-md-1"><label>Huisnummer</label><input type="number" name="woningen[${i}][huisnummer]" class="form-control"></div>
<div class="col-md-3"><label>Woonplaats</label><input type="text" name="woningen[${i}][woonplaats]" class="form-control"></div>
<div class="col-md-2"><label>WOZ waarde</label><input type="number" step="0.01" name="woningen[${i}][woz_waarde]" class="form-control"></div>
<div class="col-md-2"><label>Situatie begin jaar</label><input type="text" name="woningen[${i}][situatie_begin_jaar]" class="form-control"></div>
</div>
</div>` + "`" + `);
}
function addSchuld() {
const i = schuldenCount++;
document.getElementById('schulden-list').insertAdjacentHTML('beforeend', ` + "`" + `
<div class="array-item" id="schuld-${i}">
<button type="button" class="btn btn-sm btn-outline-danger btn-remove" onclick="removeItem('schuld-${i}')">✕</button>
<div class="row g-2">
<div class="col-md-3"><label>Bank / Geldverstrekker</label><input type="text" name="schulden[${i}][bank_of_geldverstrekker]" class="form-control"></div>
<div class="col-md-2"><label>Nummer</label><input type="text" name="schulden[${i}][nummer]" class="form-control"></div>
<div class="col-md-1"><label>Jaar</label><input type="text" name="schulden[${i}][jaar]" class="form-control"></div>
<div class="col-md-2"><label>Schuld 01-01</label><input type="number" step="0.01" name="schulden[${i}][schuld_0101]" class="form-control"></div>
<div class="col-md-2"><label>Schuld 31-12</label><input type="number" step="0.01" name="schulden[${i}][schuld_3112]" class="form-control"></div>
<div class="col-md-2"><label>Rente</label><input type="number" step="0.01" name="schulden[${i}][rente]" class="form-control"></div>
</div>
</div>` + "`" + `);
}
// Show toast on save
{{if .Saved}}
document.addEventListener('DOMContentLoaded', function() {
const t = document.getElementById('toast');
t.style.display = 'block';
setTimeout(() => t.style.display = 'none', 3000);
});
{{end}}
</script>
</body>
</html>
{{end}}
`
// Form parsing helpers
func strToBool(s string) bool {
return s == "true" || s == "on" || s == "1" || s == "ja"
}
func strToFloat(s string) float64 {
var f float64
fmt.Sscanf(s, "%f", &f)
return f
}
func strToInt(s string) int {
var i int
fmt.Sscanf(s, "%d", &i)
return i
}
func strToInt64(s string) int64 {
var i int64
fmt.Sscanf(s, "%d", &i)
return i
}
func strToDate(s string) CouchDate {
if s == "" {
return CouchDate{}
}
t, err := time.Parse("2006-01-02", s)
if err != nil {
return CouchDate{}
}
return CouchDate{t}
}
func optStr(s string) *string {
if s == "" {
return nil
}
return &s
}
// Parse array fields from form like huisgenoten[0][naam]
func parseArrayField(form map[string][]string, prefix string, index int, field string) string {
key := fmt.Sprintf("%s[%d][%s]", prefix, index, field)
if vals, ok := form[key]; ok && len(vals) > 0 {
return vals[0]
}
return ""
}
func maxArrayIndex(form map[string][]string, prefix, field string) int {
max := -1
for k := range form {
var i int
if n, _ := fmt.Sscanf(k, prefix+"[%d]["+field+"]", &i); n == 1 {
if i > max {
max = i
}
}
}
return max
}
func parseFormToAangifte(r *http.Request, existing *Aangifte) *Aangifte {
r.ParseForm()
form := map[string][]string(r.Form)
a := &Aangifte{}
if existing != nil {
a.ID = existing.ID
a.Rev = existing.Rev
a.Wachtwoord = existing.Wachtwoord
a.Burgerservicenummer = existing.Burgerservicenummer
}
if v := r.FormValue("_id"); v != "" {
a.ID = v
}
if v := r.FormValue("_rev"); v != "" {
a.Rev = v
}
a.LoginNaam = r.FormValue("login_naam")
if pw := r.FormValue("wachtwoord"); pw != "" {
a.Wachtwoord = pw
}
a.Naam = r.FormValue("naam")
a.Geboortedatum = strToDate(r.FormValue("geboortedatum"))
a.Burgerservicenummer = strToInt64(r.FormValue("burgerservicenummer"))
a.JaarAangifte = strToInt(r.FormValue("jaar_aangifte"))
a.JaarVoorJaarAangifte = a.JaarAangifte - 1
a.Telefoonnummer = optStr(r.FormValue("telefoonnummer"))
a.NummerBelastingconsulent = optStr(r.FormValue("nummer_belastingconsulent"))
a.Echtgenoot = strToBool(r.FormValue("echtgenoot"))
a.Huisgenoot = strToBool(r.FormValue("huisgenoot"))
a.SamenIngeschrevenJaNee = r.FormValue("samen_ingeschreven_ja_nee")
a.FiscalePartnerJaNee = r.FormValue("fiscale_partner_ja_nee")
a.AndereHuisgenootJaNee = r.FormValue("andere_huisgenoot_ja_nee")
a.NaamPartner = r.FormValue("naam_partner")
a.GeboortedatumPartner = strToDate(r.FormValue("geboortedatum_partner"))
a.BurgerservicenummerPartner = strToInt64(r.FormValue("burgerservicenummer_partner"))
a.OndernemingJaNee = r.FormValue("onderneming_ja_nee")
a.OndernenerIBJaNee = r.FormValue("ondernemer_ib_ja_nee")
a.GeldverstrekkerJaNee = r.FormValue("geldverstrekker_ja_nee")
a.PartnerOndernenerIBJaNee = r.FormValue("partner_ondernemer_ib_ja_nee")
a.PartnerGeldverstrekkerJaNee = r.FormValue("partner_geldverstrekker_ja_nee")
a.SchuldenJaNee = r.FormValue("schulden_ja_nee")
// Checkboxes
a.BankEnSpaarrekeningen = strToBool(r.FormValue("bank_en_spaarrekeningen_ja_nee"))
a.GroeneSpaartegoeden = strToBool(r.FormValue("groene_spaartegoeden_ja_nee"))
a.Beleggingen = strToBool(r.FormValue("beleggingen_ja_nee"))
a.GroeneBeleggingen = strToBool(r.FormValue("groene_beleggingen_ja_nee"))
a.AanmerkelijkBelang = strToBool(r.FormValue("aanmerkelijk_belang_ja_nee"))
a.Kapitaalverzekeringen = strToBool(r.FormValue("kapitaalverzekeringen_ja_nee"))
a.Bouwdepots = strToBool(r.FormValue("bouwdepots_ja_nee"))
a.ContantGeld = strToBool(r.FormValue("contant_geld_ja_nee"))
a.UitgeleendGeld = strToBool(r.FormValue("uitgeleend_geld_ja_nee"))
a.RechtenOpPeriodiekUitkeringen = strToBool(r.FormValue("rechten_op_periodieke_uitkeringen_ja_nee"))
a.OverigeBezittingen = strToBool(r.FormValue("overige_bezittingen_ja_nee"))
a.LijfrenteJaNee = strToBool(r.FormValue("lijfrente_ja_nee"))
a.InkomensvoorzieningenJaNee = strToBool(r.FormValue("inkomensvoorzieningen_ja_nee"))
a.ZorgkostenJaNee = strToBool(r.FormValue("zorgkosten_ja_nee"))
a.GiftenJaNee = strToBool(r.FormValue("giften_ja_nee"))
a.StudiefinancieringJaNee = strToBool(r.FormValue("studiefinanciering_ja_nee"))
a.AlimentatieJaNee = strToBool(r.FormValue("alimentatie_ja_nee"))
a.GehandicapteJaNee = strToBool(r.FormValue("gehandicapte_ja_nee"))
a.VoorJaarAangifteJaNee = strToBool(r.FormValue("voor_jaar_aangifte_ja_nee"))
a.WaardeHogerDanJaNee = r.FormValue("waarde_meer_dan_ja_nee")
// Arrays
maxH := maxArrayIndex(form, "huisgenoten", "naam")
for i := 0; i <= maxH; i++ {
naam := parseArrayField(form, "huisgenoten", i, "naam")
if naam == "" {
continue
}
a.Huisgenoten = append(a.Huisgenoten, Huisgenoot{
Naam: naam,
DatumVan: strToDate(parseArrayField(form, "huisgenoten", i, "datumVan")),
DatumTotEnMet: strToDate(parseArrayField(form, "huisgenoten", i, "datumTotEnMet")),
FiscalePartner: strToBool(parseArrayField(form, "huisgenoten", i, "fiscalePartner")),
Samenlevingscontract: strToBool(parseArrayField(form, "huisgenoten", i, "samenlevingscontract")),
Pensioenregeling: strToBool(parseArrayField(form, "huisgenoten", i, "pensioenregeling")),
EigenaarWoning: strToBool(parseArrayField(form, "huisgenoten", i, "eigenaarwoning")),
MinderjarigKind: strToBool(parseArrayField(form, "huisgenoten", i, "minderjarigkind")),
})
}
maxK := maxArrayIndex(form, "kinderen", "naam")
for i := 0; i <= maxK; i++ {
naam := parseArrayField(form, "kinderen", i, "naam")
if naam == "" {
continue
}
a.Kinderen = append(a.Kinderen, Kind{
Naam: naam,
Geboortedatum: strToDate(parseArrayField(form, "kinderen", i, "geboortedatum")),
Burgerservicenummer: strToInt64(parseArrayField(form, "kinderen", i, "burgerservicenummer")),
})
}
maxL := maxArrayIndex(form, "loondienst", "werkgever")
for i := 0; i <= maxL; i++ {
wg := parseArrayField(form, "loondienst", i, "werkgever")
if wg == "" {
continue
}
a.InkomstenUitLoondienst = append(a.InkomstenUitLoondienst, InkomenLoondienst{
Werkgever: wg,
SoortInkomsten: parseArrayField(form, "loondienst", i, "soortInkomsten"),
Loon: strToFloat(parseArrayField(form, "loondienst", i, "loon")),
Loonheffing: strToFloat(parseArrayField(form, "loondienst", i, "loonheffing")),
Arbeidskorting: strToFloat(parseArrayField(form, "loondienst", i, "arbeidskorting")),
Reiskosten: strToBool(parseArrayField(form, "loondienst", i, "reiskosten")),
})
}
maxP := maxArrayIndex(form, "pensioen", "verzekeringsmaatschappij")
for i := 0; i <= maxP; i++ {
mij := parseArrayField(form, "pensioen", i, "verzekeringsmaatschappij")
if mij == "" {
continue
}
a.PensioenEnAndereUitkeringen = append(a.PensioenEnAndereUitkeringen, PensioenUitkering{
Land: parseArrayField(form, "pensioen", i, "land"),
Verzekeringsmaatschappij: mij,
Loon: strToFloat(parseArrayField(form, "pensioen", i, "loon")),
Loonheffing: strToFloat(parseArrayField(form, "pensioen", i, "loonheffing")),
})
}
maxW := maxArrayIndex(form, "woningen", "woonplaats")
for i := 0; i <= maxW; i++ {
wp := parseArrayField(form, "woningen", i, "woonplaats")
if wp == "" {
continue
}
a.Woningen = append(a.Woningen, Woning{
Land: parseArrayField(form, "woningen", i, "land"),
Postcode: parseArrayField(form, "woningen", i, "postcode"),
Huisnummer: strToInt(parseArrayField(form, "woningen", i, "huisnummer")),
Woonplaats: wp,
WozWaarde: strToFloat(parseArrayField(form, "woningen", i, "woz_waarde")),
SituatieBeginJaar: parseArrayField(form, "woningen", i, "situatie_begin_jaar"),
Eigenaar: strToBool(parseArrayField(form, "woningen", i, "eigenaar")),
Verhuurd: strToBool(parseArrayField(form, "woningen", i, "verhuurd")),
Erfpachtcanon: strToBool(parseArrayField(form, "woningen", i, "erfpachtcanon")),
})
}
maxS := maxArrayIndex(form, "schulden", "bank_of_geldverstrekker")
for i := 0; i <= maxS; i++ {
bank := parseArrayField(form, "schulden", i, "bank_of_geldverstrekker")
if bank == "" {
continue
}
a.Schulden = append(a.Schulden, Schuld{
BankOfGeldverstrekker: bank,
Nummer: parseArrayField(form, "schulden", i, "nummer"),
Jaar: parseArrayField(form, "schulden", i, "jaar"),
Schuld0101: strToFloat(parseArrayField(form, "schulden", i, "schuld_0101")),
Schuld3112: strToFloat(parseArrayField(form, "schulden", i, "schuld_3112")),
Rente: strToFloat(parseArrayField(form, "schulden", i, "rente")),
SchuldVoorWoningJaNee: parseArrayField(form, "schulden", i, "schuld_voor_woning_ja_nee"),
SchuldVanBeidenJaNee: parseArrayField(form, "schulden", i, "schuld_van_beiden_ja_nee"),
LeningVoorWoningJaNee: "ja",
LeningGeheelVoorWoningJaNee: "ja",
})
}
return a
}
// Handlers
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
}
// New aangifte
if aangifte == nil {
aangifte = &Aangifte{
Burgerservicenummer: bsn,
Wachtwoord: wachtwoord,
JaarAangifte: time.Now().Year() - 1,
JaarVoorJaarAangifte: time.Now().Year() - 2,
Huisgenoten: []Huisgenoot{},
Kinderen: []Kind{},
InkomstenUitLoondienst: []InkomenLoondienst{},
PensioenEnAndereUitkeringen: []PensioenUitkering{},
Woningen: []Woning{},
Schulden: []Schuld{},
}
}
// Create session
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)
}
func handleAangifte(w http.ResponseWriter, r *http.Request) {
sess := getSession(r)
if sess == nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
type PageData struct {
*Aangifte
Saved bool
}
saved := r.URL.Query().Get("saved") == "1"
tmpl.ExecuteTemplate(w, "aangifte", PageData{sess.Aangifte, saved})
}
func handleSave(w http.ResponseWriter, r *http.Request) {
sess := getSession(r)
if sess == nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
aangifte := parseFormToAangifte(r, sess.Aangifte)
if err := saveAangifte(aangifte); err != nil {
log.Printf("save error: %v", err)
http.Error(w, "Opslaan mislukt", http.StatusInternalServerError)
return
}
sess.Aangifte = aangifte
http.Redirect(w, r, "/aangifte?saved=1", http.StatusSeeOther)
}
func handleLogout(w http.ResponseWriter, r *http.Request) {
if id := sessionID(r); id != "" {
delete(sessions, id)
}
http.SetCookie(w, &http.Cookie{Name: "session", Value: "", MaxAge: -1, Path: "/"})
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func main() {
if err := ensureDB(); err != nil {
log.Fatalf("Cannot connect to CouchDB: %v", err)
}
if err := ensureBSNIndex(); err != nil {
log.Printf("Warning: could not create BSN index: %v", err)
}
http.HandleFunc("/", handleLogin)
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/aangifte", handleAangifte)
http.HandleFunc("/save", handleSave)
http.HandleFunc("/logout", handleLogout)
log.Printf("Starting on %s", listenAddr)
log.Fatal(http.ListenAndServe(listenAddr, nil))
}