456 lines
15 KiB
Go
456 lines
15 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func (a *App) handleSetDaysPerMonth(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/general", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
days := mustInt(r.FormValue("days_per_month"), 2)
|
|
if days < 1 || days > 31 {
|
|
redirectWithMessage(w, r, "/general", "error", "Tage pro Monat 1-31")
|
|
return
|
|
}
|
|
if _, err := a.db.Exec(`UPDATE settings SET days_per_month=? WHERE id=1`, days); err != nil {
|
|
redirectWithMessage(w, r, "/general", "error", "Einstellung nicht gespeichert")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/general", "info", "Tage pro Monat gespeichert")
|
|
}
|
|
|
|
func (a *App) handleSetCurrentTime(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
settings, err := a.getSettings()
|
|
if err != nil {
|
|
redirectWithMessage(w, r, "/", "error", "Einstellungen nicht lesbar")
|
|
return
|
|
}
|
|
month := mustInt(r.FormValue("current_month"), 1)
|
|
day := mustInt(r.FormValue("current_day"), 1)
|
|
if month < 1 || month > 12 || day < 1 || day > settings.DaysPerMonth {
|
|
redirectWithMessage(w, r, "/", "error", "Ingame-Zeit ungueltig")
|
|
return
|
|
}
|
|
if _, err := a.db.Exec(`UPDATE settings SET current_month=?, current_day=? WHERE id=1`, month, day); err != nil {
|
|
redirectWithMessage(w, r, "/", "error", "Ingame-Zeit nicht gespeichert")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/", "info", "Ingame-Zeit gespeichert")
|
|
}
|
|
|
|
func (a *App) handleCreateField(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
number := mustInt(r.FormValue("number"), 0)
|
|
name := strings.TrimSpace(r.FormValue("name"))
|
|
owned := r.FormValue("owned") == "on"
|
|
if number <= 0 {
|
|
redirectWithMessage(w, r, "/fields", "error", "Feldnummer ungueltig")
|
|
return
|
|
}
|
|
if _, err := a.db.Exec(`INSERT INTO fields(number,name,owned) VALUES (?,?,?)`, number, name, owned); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Feld nicht angelegt")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/fields", "info", "Feld angelegt")
|
|
}
|
|
|
|
func (a *App) handleUpdateField(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
id := mustInt64(r.FormValue("id"), 0)
|
|
name := strings.TrimSpace(r.FormValue("name"))
|
|
owned := r.FormValue("owned") == "on"
|
|
if id <= 0 {
|
|
redirectWithMessage(w, r, "/fields", "error", "Feld-ID ungueltig")
|
|
return
|
|
}
|
|
if owned {
|
|
if _, err := a.db.Exec(`UPDATE fields SET name=?,owned=? WHERE id=?`, name, owned, id); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Feld nicht aktualisiert")
|
|
return
|
|
}
|
|
} else {
|
|
if _, err := a.db.Exec(`UPDATE fields SET name=?,owned=?,group_key='',group_name='' WHERE id=?`, name, owned, id); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Feld nicht aktualisiert")
|
|
return
|
|
}
|
|
}
|
|
redirectWithMessage(w, r, "/fields", "info", "Feld aktualisiert")
|
|
}
|
|
|
|
func (a *App) handleDeleteField(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
id := mustInt64(r.FormValue("id"), 0)
|
|
if id <= 0 {
|
|
redirectWithMessage(w, r, "/fields", "error", "Feld-ID ungueltig")
|
|
return
|
|
}
|
|
if _, err := a.db.Exec(`DELETE FROM fields WHERE id=?`, id); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Feld nicht geloescht")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/fields", "info", "Feld geloescht")
|
|
}
|
|
|
|
func (a *App) handleCreateFieldGroup(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
ids, err := parseInt64List(r.Form["field_ids"])
|
|
if err != nil || len(ids) == 0 {
|
|
redirectWithMessage(w, r, "/fields", "error", "Mindestens ein Feld auswaehlen")
|
|
return
|
|
}
|
|
fields, err := a.getFieldsByIDs(ids)
|
|
if err != nil || len(fields) != len(ids) {
|
|
redirectWithMessage(w, r, "/fields", "error", "Felder nicht gefunden")
|
|
return
|
|
}
|
|
name := strings.TrimSpace(r.FormValue("name"))
|
|
if name == "" {
|
|
name = autoGroupName(fields)
|
|
}
|
|
groupKey := fmt.Sprintf("g%d", time.Now().UnixNano())
|
|
|
|
tx, err := a.db.Begin()
|
|
if err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Gruppierung fehlgeschlagen")
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
for _, f := range fields {
|
|
if _, err := tx.Exec(`UPDATE fields SET group_key=?,group_name=? WHERE id=?`, groupKey, name, f.ID); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Gruppierung fehlgeschlagen")
|
|
return
|
|
}
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Gruppierung fehlgeschlagen")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/fields", "info", "Feldgruppe gespeichert")
|
|
}
|
|
|
|
func (a *App) handleDeleteFieldGroup(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
key := strings.TrimSpace(r.FormValue("group_key"))
|
|
if key == "" {
|
|
redirectWithMessage(w, r, "/fields", "error", "Gruppen-ID ungueltig")
|
|
return
|
|
}
|
|
if _, err := a.db.Exec(`UPDATE fields SET group_key='',group_name='' WHERE group_key=?`, key); err != nil {
|
|
redirectWithMessage(w, r, "/fields", "error", "Gruppe nicht aufgeloest")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/fields", "info", "Gruppe aufgeloest")
|
|
}
|
|
|
|
func (a *App) handleCreateCrop(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
name := strings.TrimSpace(r.FormValue("name"))
|
|
start := mustInt(r.FormValue("sow_start_month"), 0)
|
|
end := mustInt(r.FormValue("sow_end_month"), 0)
|
|
grow := mustInt(r.FormValue("grow_months"), 0)
|
|
regrowEnabled := r.FormValue("regrow_enabled") == "on"
|
|
regrowCycles := mustInt(r.FormValue("regrow_cycles"), 0)
|
|
if err := validateCropInput(name, start, end, grow, regrowCycles); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", err.Error())
|
|
return
|
|
}
|
|
if !regrowEnabled {
|
|
regrowCycles = 0
|
|
}
|
|
if _, err := a.db.Exec(`INSERT INTO crops(name,sow_start_month,sow_end_month,grow_months,regrow_enabled,regrow_cycles) VALUES (?,?,?,?,?,?)`, name, start, end, grow, regrowEnabled, regrowCycles); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", "Feldfrucht nicht angelegt")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/crops", "info", "Feldfrucht angelegt")
|
|
}
|
|
|
|
func (a *App) handleUpdateCrop(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
id := mustInt64(r.FormValue("id"), 0)
|
|
name := strings.TrimSpace(r.FormValue("name"))
|
|
start := mustInt(r.FormValue("sow_start_month"), 0)
|
|
end := mustInt(r.FormValue("sow_end_month"), 0)
|
|
grow := mustInt(r.FormValue("grow_months"), 0)
|
|
regrowEnabled := r.FormValue("regrow_enabled") == "on"
|
|
regrowCycles := mustInt(r.FormValue("regrow_cycles"), 0)
|
|
if id <= 0 {
|
|
redirectWithMessage(w, r, "/crops", "error", "Feldfrucht-ID ungueltig")
|
|
return
|
|
}
|
|
if err := validateCropInput(name, start, end, grow, regrowCycles); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", err.Error())
|
|
return
|
|
}
|
|
if !regrowEnabled {
|
|
regrowCycles = 0
|
|
}
|
|
if _, err := a.db.Exec(`UPDATE crops SET name=?,sow_start_month=?,sow_end_month=?,grow_months=?,regrow_enabled=?,regrow_cycles=? WHERE id=?`, name, start, end, grow, regrowEnabled, regrowCycles, id); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", "Feldfrucht nicht aktualisiert")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/crops", "info", "Feldfrucht aktualisiert")
|
|
}
|
|
|
|
func (a *App) handleDeleteCrop(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
id := mustInt64(r.FormValue("id"), 0)
|
|
if id <= 0 {
|
|
redirectWithMessage(w, r, "/crops", "error", "Feldfrucht-ID ungueltig")
|
|
return
|
|
}
|
|
if _, err := a.db.Exec(`DELETE FROM crops WHERE id=?`, id); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", "Feldfrucht nicht geloescht")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/crops", "info", "Feldfrucht geloescht")
|
|
}
|
|
|
|
func (a *App) handleCreatePlan(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/planning", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
targetRef := strings.TrimSpace(r.FormValue("target_ref"))
|
|
cropID := mustInt64(r.FormValue("crop_id"), 0)
|
|
startMonth := mustInt(r.FormValue("start_month"), 1)
|
|
startDay := mustInt(r.FormValue("start_day"), 1)
|
|
notes := strings.TrimSpace(r.FormValue("notes"))
|
|
if targetRef == "" || cropID <= 0 {
|
|
redirectWithMessage(w, r, "/planning", "error", "Planungsziel oder Feldfrucht ungueltig")
|
|
return
|
|
}
|
|
|
|
settings, err := a.getSettings()
|
|
if err != nil {
|
|
redirectWithMessage(w, r, "/planning", "error", "Einstellungen nicht lesbar")
|
|
return
|
|
}
|
|
if startMonth < 1 || startMonth > 12 || startDay < 1 || startDay > settings.DaysPerMonth {
|
|
redirectWithMessage(w, r, "/planning", "error", "Startdatum ungueltig")
|
|
return
|
|
}
|
|
|
|
var c Crop
|
|
err = a.db.QueryRow(`SELECT id,name,sow_start_month,sow_end_month,grow_months FROM crops WHERE id=?`, cropID).
|
|
Scan(&c.ID, &c.Name, &c.SowStartMonth, &c.SowEndMonth, &c.GrowMonths)
|
|
if err != nil {
|
|
redirectWithMessage(w, r, "/planning", "error", "Feldfrucht nicht gefunden")
|
|
return
|
|
}
|
|
if !monthInWindow(startMonth, c.SowStartMonth, c.SowEndMonth) {
|
|
redirectWithMessage(w, r, "/planning", "error", "Aussaat ausserhalb des Zeitfensters")
|
|
return
|
|
}
|
|
|
|
fieldID, targetName, err := a.resolvePlanningTarget(targetRef)
|
|
if err != nil {
|
|
redirectWithMessage(w, r, "/planning", "error", err.Error())
|
|
return
|
|
}
|
|
|
|
harvestMonth := wrapMonth(startMonth + c.GrowMonths)
|
|
harvestDay := startDay
|
|
_, err = a.db.Exec(
|
|
`INSERT INTO plans(field_id,target_ref,target_name,crop_id,start_month,start_day,harvest_month,harvest_day,notes) VALUES (?,?,?,?,?,?,?,?,?)`,
|
|
fieldID, targetRef, targetName, cropID, startMonth, startDay, harvestMonth, harvestDay, notes,
|
|
)
|
|
if err != nil {
|
|
redirectWithMessage(w, r, "/planning", "error", "Plan nicht gespeichert")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/planning", "info", "Plan gespeichert")
|
|
}
|
|
|
|
func (a *App) handleDeletePlan(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/planning", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
id := mustInt64(r.FormValue("id"), 0)
|
|
if id <= 0 {
|
|
redirectWithMessage(w, r, "/planning", "error", "Plan-ID ungueltig")
|
|
return
|
|
}
|
|
if _, err := a.db.Exec(`DELETE FROM plans WHERE id=?`, id); err != nil {
|
|
redirectWithMessage(w, r, "/planning", "error", "Plan nicht geloescht")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/planning", "info", "Plan geloescht")
|
|
}
|
|
|
|
func (a *App) handleCreateCropStep(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
cropID := mustInt64(r.FormValue("crop_id"), 0)
|
|
phase := strings.TrimSpace(r.FormValue("phase"))
|
|
offset := mustInt(r.FormValue("month_offset"), -1)
|
|
title := strings.TrimSpace(r.FormValue("title"))
|
|
if cropID <= 0 || (phase != "pre" && phase != "post") || offset < 0 || offset > 24 || title == "" {
|
|
redirectWithMessage(w, r, "/crops", "error", "Vor/Nachbereitung ungueltig")
|
|
return
|
|
}
|
|
if _, err := a.db.Exec(`INSERT INTO crop_steps(crop_id,phase,month_offset,title) VALUES (?,?,?,?)`, cropID, phase, offset, title); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", "Schritt nicht gespeichert")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/crops", "info", "Schritt gespeichert")
|
|
}
|
|
|
|
func (a *App) handleDeleteCropStep(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", "Form ungueltig")
|
|
return
|
|
}
|
|
id := mustInt64(r.FormValue("id"), 0)
|
|
if id <= 0 {
|
|
redirectWithMessage(w, r, "/crops", "error", "Schritt-ID ungueltig")
|
|
return
|
|
}
|
|
if _, err := a.db.Exec(`DELETE FROM crop_steps WHERE id=?`, id); err != nil {
|
|
redirectWithMessage(w, r, "/crops", "error", "Schritt nicht geloescht")
|
|
return
|
|
}
|
|
redirectWithMessage(w, r, "/crops", "info", "Schritt geloescht")
|
|
}
|
|
|
|
func (a *App) resolvePlanningTarget(ref string) (sql.NullInt64, string, error) {
|
|
if strings.HasPrefix(ref, "f:") {
|
|
id := mustInt64(strings.TrimPrefix(ref, "f:"), 0)
|
|
if id <= 0 {
|
|
return sql.NullInt64{}, "", errors.New("Feld-Ziel ungueltig")
|
|
}
|
|
var f Field
|
|
err := a.db.QueryRow(`SELECT id,number,name,owned FROM fields WHERE id=?`, id).Scan(&f.ID, &f.Number, &f.Name, &f.Owned)
|
|
if err != nil {
|
|
return sql.NullInt64{}, "", errors.New("Feld nicht gefunden")
|
|
}
|
|
if !f.Owned {
|
|
return sql.NullInt64{}, "", errors.New("Feld ist nicht im Besitz")
|
|
}
|
|
return sql.NullInt64{Int64: f.ID, Valid: true}, fieldLabel(f), nil
|
|
}
|
|
if strings.HasPrefix(ref, "g:") {
|
|
key := strings.TrimPrefix(ref, "g:")
|
|
if key == "" {
|
|
return sql.NullInt64{}, "", errors.New("Gruppen-Ziel ungueltig")
|
|
}
|
|
rows, err := a.db.Query(`SELECT number FROM fields WHERE group_key=? AND owned=1 ORDER BY number`, key)
|
|
if err != nil {
|
|
return sql.NullInt64{}, "", errors.New("Gruppe nicht lesbar")
|
|
}
|
|
defer rows.Close()
|
|
var nums []string
|
|
for rows.Next() {
|
|
var n int
|
|
if err := rows.Scan(&n); err != nil {
|
|
return sql.NullInt64{}, "", errors.New("Gruppe nicht lesbar")
|
|
}
|
|
nums = append(nums, strconv.Itoa(n))
|
|
}
|
|
if len(nums) == 0 {
|
|
return sql.NullInt64{}, "", errors.New("Gruppe hat keine Felder im Besitz")
|
|
}
|
|
var groupName string
|
|
_ = a.db.QueryRow(`SELECT COALESCE(group_name,'') FROM fields WHERE group_key=? LIMIT 1`, key).Scan(&groupName)
|
|
if strings.TrimSpace(groupName) == "" {
|
|
groupName = "Feld " + strings.Join(nums, "+")
|
|
}
|
|
return sql.NullInt64{}, groupName, nil
|
|
}
|
|
return sql.NullInt64{}, "", errors.New("Planungsziel ungueltig")
|
|
}
|