1315 lines
56 KiB
Go
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))
|
|
}
|