Fortlaufende Zyklus-Planung und Periodenlogik einführen

This commit is contained in:
Kai
2026-02-16 15:06:34 +01:00
parent 2255318993
commit 67bd87e12a
9 changed files with 138 additions and 63 deletions

View File

@@ -13,14 +13,16 @@ func buildTasksForDay(plans []Plan, crops []Crop, products []Product, stepMap ma
return tasks return tasks
} }
func buildCalendar(plans []Plan, crops []Crop, products []Product, stepMap map[int64][]CropStep, customTasks []CustomTask, doneMap map[string]bool, startMonth, startDay, daysPerMonth, spanMonths int) []CalendarMonth { func buildCalendar(plans []Plan, crops []Crop, products []Product, stepMap map[int64][]CropStep, customTasks []CustomTask, doneMap map[string]bool, startPeriod, startDay, daysPerMonth, spanMonths int) []CalendarMonth {
var out []CalendarMonth var out []CalendarMonth
startCycle := periodCycle(startPeriod)
for offset := 0; offset < spanMonths; offset++ { for offset := 0; offset < spanMonths; offset++ {
month := wrapMonth(startMonth + offset) period := startPeriod + offset
yearOffset := (startMonth - 1 + offset) / 12 month := periodMonth(period)
yearOffset := periodCycle(period) - startCycle
label := monthNames[month-1] label := monthNames[month-1]
if yearOffset > 0 { if yearOffset > 0 {
label = fmt.Sprintf("%s (+%d Jahr)", label, yearOffset) label = fmt.Sprintf("%s (+%d Zyklus)", label, yearOffset)
} }
fromDay := 1 fromDay := 1
@@ -30,7 +32,7 @@ func buildCalendar(plans []Plan, crops []Crop, products []Product, stepMap map[i
var days []CalendarDay var days []CalendarDay
for d := fromDay; d <= daysPerMonth; d++ { for d := fromDay; d <= daysPerMonth; d++ {
items := collectTasksForDate(plans, crops, products, stepMap, customTasks, month, d, yearOffset) items := collectTasksForDate(plans, crops, products, stepMap, customTasks, period, d, yearOffset)
applyCompletion(items, doneMap) applyCompletion(items, doneMap)
sortTasks(items) sortTasks(items)
days = append(days, CalendarDay{Day: d, Tasks: items, Groups: groupTasksByField(items)}) days = append(days, CalendarDay{Day: d, Tasks: items, Groups: groupTasksByField(items)})
@@ -65,11 +67,12 @@ func groupTasksByField(tasks []Task) []FieldTaskGroup {
return out return out
} }
func collectTasksForDate(plans []Plan, crops []Crop, products []Product, stepMap map[int64][]CropStep, customTasks []CustomTask, month, day, yearOffset int) []Task { func collectTasksForDate(plans []Plan, crops []Crop, products []Product, stepMap map[int64][]CropStep, customTasks []CustomTask, period, day, yearOffset int) []Task {
var tasks []Task var tasks []Task
month := periodMonth(period)
for _, p := range plans { for _, p := range plans {
tasks = append(tasks, expandPlanDayTasks(p, stepMap[p.CropID], month, day, yearOffset)...) tasks = append(tasks, expandPlanDayTasks(p, stepMap[p.CropID], period, day, yearOffset)...)
} }
if day == 1 { if day == 1 {
for _, c := range crops { for _, c := range crops {
@@ -102,7 +105,7 @@ func collectTasksForDate(plans []Plan, crops []Crop, products []Product, stepMap
} }
} }
for _, c := range customTasks { for _, c := range customTasks {
if c.Month == month && c.Day == day && c.YearOffset == yearOffset { if c.TaskPeriod == period && c.Day == day {
field := c.TargetName field := c.TargetName
if strings.TrimSpace(field) == "" { if strings.TrimSpace(field) == "" {
field = "Allgemein" field = "Allgemein"
@@ -113,13 +116,13 @@ func collectTasksForDate(plans []Plan, crops []Crop, products []Product, stepMap
} }
tasks = append(tasks, Task{ tasks = append(tasks, Task{
UID: fmt.Sprintf("custom:%d", c.ID), UID: fmt.Sprintf("custom:%d", c.ID),
Type: "Aufgabe", Type: "Aufgabe",
Field: field, Field: field,
Message: msg, Message: msg,
Month: month, Month: periodMonth(period),
Day: day, Day: day,
YearOffset: yearOffset, YearOffset: yearOffset,
SortOrder: 40, SortOrder: 40,
}) })
} }
} }
@@ -127,18 +130,18 @@ func collectTasksForDate(plans []Plan, crops []Crop, products []Product, stepMap
return tasks return tasks
} }
func expandPlanDayTasks(p Plan, steps []CropStep, month, day, yearOffset int) []Task { func expandPlanDayTasks(p Plan, steps []CropStep, period, day, yearOffset int) []Task {
field := p.TargetName field := p.TargetName
if strings.TrimSpace(field) == "" { if strings.TrimSpace(field) == "" {
field = "Unbekanntes Feld" field = "Unbekanntes Feld"
} }
month := periodMonth(period)
var out []Task var out []Task
baseHarvest := p.HarvestMonth
if p.StartMonth == month && p.StartDay == day && yearOffset == 0 { if p.StartPeriod == period && p.StartDay == day {
out = append(out, Task{ out = append(out, Task{
UID: fmt.Sprintf("plan:%d:sow:0", p.ID), UID: fmt.Sprintf("plan:%d:sow:%d", p.ID, p.StartPeriod),
Type: "Aussaat", Type: "Aussaat",
Field: field, Field: field,
Message: withOptionalNote(fmt.Sprintf("Aussaat %s", p.CropName), p.Notes), Message: withOptionalNote(fmt.Sprintf("Aussaat %s", p.CropName), p.Notes),
@@ -149,21 +152,21 @@ func expandPlanDayTasks(p Plan, steps []CropStep, month, day, yearOffset int) []
}) })
} }
harvestMonths := []int{baseHarvest} harvestPeriods := []int{p.HarvestPeriod}
if p.RegrowEnabled { if p.RegrowEnabled {
maxExtra := p.RegrowCycles maxExtra := p.RegrowCycles
if maxExtra == 0 { if maxExtra == 0 {
maxExtra = 24 maxExtra = 24
} }
for i := 1; i <= maxExtra; i++ { for i := 1; i <= maxExtra; i++ {
harvestMonths = append(harvestMonths, wrapMonth(baseHarvest+(i*p.HarvestDistance()))) harvestPeriods = append(harvestPeriods, p.HarvestPeriod+(i*p.HarvestDistance()))
} }
} }
harvestMonths = uniqueMonths(harvestMonths) harvestPeriods = uniquePeriods(harvestPeriods)
for idx, hm := range harvestMonths { for _, harvestPeriod := range harvestPeriods {
if hm == month && p.HarvestDay == day { if harvestPeriod == period && p.HarvestDay == day {
uid := fmt.Sprintf("plan:%d:harvest:%d", p.ID, idx) uid := fmt.Sprintf("plan:%d:harvest:%d", p.ID, harvestPeriod)
out = append(out, Task{ out = append(out, Task{
UID: uid, UID: uid,
Type: "Ernte", Type: "Ernte",
@@ -180,10 +183,10 @@ func expandPlanDayTasks(p Plan, steps []CropStep, month, day, yearOffset int) []
for _, s := range steps { for _, s := range steps {
switch s.Phase { switch s.Phase {
case "pre": case "pre":
taskMonth := wrapMonth(p.StartMonth - s.MonthOffset) taskPeriod := p.StartPeriod - s.MonthOffset
if taskMonth == month && p.StartDay == day && yearOffset == 0 { if taskPeriod == period && p.StartDay == day {
out = append(out, Task{ out = append(out, Task{
UID: fmt.Sprintf("plan:%d:pre:%d", p.ID, s.ID), UID: fmt.Sprintf("plan:%d:pre:%d:%d", p.ID, s.ID, taskPeriod),
Type: "Vorbereitung", Type: "Vorbereitung",
Field: field, Field: field,
Message: s.Title, Message: s.Title,
@@ -194,11 +197,11 @@ func expandPlanDayTasks(p Plan, steps []CropStep, month, day, yearOffset int) []
}) })
} }
case "post": case "post":
for idx, hm := range harvestMonths { for _, harvestPeriod := range harvestPeriods {
taskMonth := wrapMonth(hm + s.MonthOffset) taskPeriod := harvestPeriod + s.MonthOffset
if taskMonth == month && p.HarvestDay == day { if taskPeriod == period && p.HarvestDay == day {
out = append(out, Task{ out = append(out, Task{
UID: fmt.Sprintf("plan:%d:post:%d:%d", p.ID, s.ID, idx), UID: fmt.Sprintf("plan:%d:post:%d:%d", p.ID, s.ID, taskPeriod),
Type: "Nachbereitung", Type: "Nachbereitung",
Field: field, Field: field,
Message: s.Title, Message: s.Title,
@@ -252,7 +255,7 @@ func sortTasks(tasks []Task) {
}) })
} }
func uniqueMonths(values []int) []int { func uniquePeriods(values []int) []int {
seen := make(map[int]bool) seen := make(map[int]bool)
var out []int var out []int
for _, v := range values { for _, v := range values {

View File

@@ -11,12 +11,15 @@ func ensureSchema(db *sql.DB) error {
id TINYINT PRIMARY KEY, id TINYINT PRIMARY KEY,
days_per_month INT NOT NULL DEFAULT 2, days_per_month INT NOT NULL DEFAULT 2,
current_month TINYINT NOT NULL DEFAULT 1, current_month TINYINT NOT NULL DEFAULT 1,
current_cycle INT NOT NULL DEFAULT 0,
current_day TINYINT NOT NULL DEFAULT 1 current_day TINYINT NOT NULL DEFAULT 1
)`, )`,
`ALTER TABLE settings ADD COLUMN IF NOT EXISTS current_month TINYINT NOT NULL DEFAULT 1`, `ALTER TABLE settings ADD COLUMN IF NOT EXISTS current_month TINYINT NOT NULL DEFAULT 1`,
`ALTER TABLE settings ADD COLUMN IF NOT EXISTS current_cycle INT NOT NULL DEFAULT 0`,
`ALTER TABLE settings ADD COLUMN IF NOT EXISTS current_day TINYINT NOT NULL DEFAULT 1`, `ALTER TABLE settings ADD COLUMN IF NOT EXISTS current_day TINYINT NOT NULL DEFAULT 1`,
`INSERT INTO settings(id,days_per_month,current_month,current_day) VALUES (1,2,1,1) ON DUPLICATE KEY UPDATE id=id`, `INSERT INTO settings(id,days_per_month,current_month,current_cycle,current_day) VALUES (1,2,1,0,1) ON DUPLICATE KEY UPDATE id=id`,
`UPDATE settings SET current_month=1 WHERE current_month < 1 OR current_month > 12`, `UPDATE settings SET current_month=1 WHERE current_month < 1 OR current_month > 12`,
`UPDATE settings SET current_cycle=0 WHERE current_cycle < 0`,
`UPDATE settings SET current_day=1 WHERE current_day < 1`, `UPDATE settings SET current_day=1 WHERE current_day < 1`,
`CREATE TABLE IF NOT EXISTS fields( `CREATE TABLE IF NOT EXISTS fields(
@@ -71,6 +74,7 @@ func ensureSchema(db *sql.DB) error {
id BIGINT AUTO_INCREMENT PRIMARY KEY, id BIGINT AUTO_INCREMENT PRIMARY KEY,
template_id BIGINT NULL, template_id BIGINT NULL,
title VARCHAR(140) NOT NULL, title VARCHAR(140) NOT NULL,
task_period INT NOT NULL DEFAULT 0,
month TINYINT NOT NULL, month TINYINT NOT NULL,
day TINYINT NOT NULL, day TINYINT NOT NULL,
year_offset SMALLINT NOT NULL DEFAULT 0, year_offset SMALLINT NOT NULL DEFAULT 0,
@@ -95,8 +99,10 @@ func ensureSchema(db *sql.DB) error {
target_ref VARCHAR(80) NOT NULL DEFAULT '', target_ref VARCHAR(80) NOT NULL DEFAULT '',
target_name VARCHAR(140) NOT NULL DEFAULT '', target_name VARCHAR(140) NOT NULL DEFAULT '',
crop_id BIGINT NOT NULL, crop_id BIGINT NOT NULL,
start_period INT NOT NULL DEFAULT 0,
start_month TINYINT NOT NULL, start_month TINYINT NOT NULL,
start_day TINYINT NOT NULL, start_day TINYINT NOT NULL,
harvest_period INT NOT NULL DEFAULT 0,
harvest_month TINYINT NOT NULL, harvest_month TINYINT NOT NULL,
harvest_day TINYINT NOT NULL, harvest_day TINYINT NOT NULL,
notes VARCHAR(255) NOT NULL DEFAULT '', notes VARCHAR(255) NOT NULL DEFAULT '',
@@ -107,6 +113,12 @@ func ensureSchema(db *sql.DB) error {
`ALTER TABLE plans MODIFY COLUMN field_id BIGINT NULL`, `ALTER TABLE plans MODIFY COLUMN field_id BIGINT NULL`,
`ALTER TABLE plans ADD COLUMN IF NOT EXISTS target_ref VARCHAR(80) NOT NULL DEFAULT '' AFTER field_id`, `ALTER TABLE plans ADD COLUMN IF NOT EXISTS target_ref VARCHAR(80) NOT NULL DEFAULT '' AFTER field_id`,
`ALTER TABLE plans ADD COLUMN IF NOT EXISTS target_name VARCHAR(140) NOT NULL DEFAULT '' AFTER target_ref`, `ALTER TABLE plans ADD COLUMN IF NOT EXISTS target_name VARCHAR(140) NOT NULL DEFAULT '' AFTER target_ref`,
`ALTER TABLE plans ADD COLUMN IF NOT EXISTS start_period INT NOT NULL DEFAULT 0 AFTER crop_id`,
`ALTER TABLE plans ADD COLUMN IF NOT EXISTS harvest_period INT NOT NULL DEFAULT 0 AFTER start_day`,
`UPDATE plans SET start_period = start_month - 1 WHERE start_period = 0 AND start_month <> 1`,
`UPDATE plans SET harvest_period = start_period + (CASE WHEN harvest_month >= start_month THEN harvest_month - start_month ELSE harvest_month + 12 - start_month END) WHERE harvest_period = 0`,
`ALTER TABLE custom_tasks ADD COLUMN IF NOT EXISTS task_period INT NOT NULL DEFAULT 0 AFTER title`,
`UPDATE custom_tasks SET task_period = (year_offset * 12) + (month - 1) WHERE task_period = 0 AND (year_offset <> 0 OR month <> 1)`,
} }
for _, stmt := range stmts { for _, stmt := range stmts {
if _, err := db.Exec(stmt); err != nil { if _, err := db.Exec(stmt); err != nil {
@@ -118,8 +130,8 @@ func ensureSchema(db *sql.DB) error {
func (a *App) getSettings() (Settings, error) { func (a *App) getSettings() (Settings, error) {
var s Settings var s Settings
err := a.db.QueryRow(`SELECT days_per_month,current_month,current_day FROM settings WHERE id=1`). err := a.db.QueryRow(`SELECT days_per_month,current_month,current_cycle,current_day FROM settings WHERE id=1`).
Scan(&s.DaysPerMonth, &s.CurrentMonth, &s.CurrentDay) Scan(&s.DaysPerMonth, &s.CurrentMonth, &s.CurrentCycle, &s.CurrentDay)
return s, err return s, err
} }
@@ -199,10 +211,10 @@ func (a *App) listProducts() ([]Product, error) {
func (a *App) listPlans() ([]Plan, error) { func (a *App) listPlans() ([]Plan, error) {
rows, err := a.db.Query(` rows, err := a.db.Query(`
SELECT p.id,p.field_id,COALESCE(p.target_ref,''),COALESCE(p.target_name,''),p.crop_id,COALESCE(c.name,''),COALESCE(c.grow_months,1),p.start_month,p.start_day,p.harvest_month,p.harvest_day,COALESCE(p.notes,''),COALESCE(c.regrow_enabled,0),COALESCE(c.regrow_cycles,0) SELECT p.id,p.field_id,COALESCE(p.target_ref,''),COALESCE(p.target_name,''),p.crop_id,COALESCE(c.name,''),COALESCE(c.grow_months,1),p.start_period,p.start_month,p.start_day,p.harvest_period,p.harvest_month,p.harvest_day,COALESCE(p.notes,''),COALESCE(c.regrow_enabled,0),COALESCE(c.regrow_cycles,0)
FROM plans p FROM plans p
JOIN crops c ON c.id=p.crop_id JOIN crops c ON c.id=p.crop_id
ORDER BY p.start_month,p.start_day,p.id DESC`) ORDER BY p.start_period,p.start_day,p.id DESC`)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -210,9 +222,11 @@ func (a *App) listPlans() ([]Plan, error) {
var out []Plan var out []Plan
for rows.Next() { for rows.Next() {
var p Plan var p Plan
if err := rows.Scan(&p.ID, &p.FieldID, &p.TargetRef, &p.TargetName, &p.CropID, &p.CropName, &p.GrowMonths, &p.StartMonth, &p.StartDay, &p.HarvestMonth, &p.HarvestDay, &p.Notes, &p.RegrowEnabled, &p.RegrowCycles); err != nil { if err := rows.Scan(&p.ID, &p.FieldID, &p.TargetRef, &p.TargetName, &p.CropID, &p.CropName, &p.GrowMonths, &p.StartPeriod, &p.StartMonth, &p.StartDay, &p.HarvestPeriod, &p.HarvestMonth, &p.HarvestDay, &p.Notes, &p.RegrowEnabled, &p.RegrowCycles); err != nil {
return nil, err return nil, err
} }
p.StartCycle = periodCycle(p.StartPeriod)
p.HarvestCycle = periodCycle(p.HarvestPeriod)
out = append(out, p) out = append(out, p)
} }
return out, rows.Err() return out, rows.Err()
@@ -272,9 +286,9 @@ func (a *App) listCustomTaskTemplates() ([]CustomTaskTemplate, error) {
func (a *App) listCustomTasks() ([]CustomTask, error) { func (a *App) listCustomTasks() ([]CustomTask, error) {
rows, err := a.db.Query(` rows, err := a.db.Query(`
SELECT id,template_id,title,month,day,year_offset,COALESCE(target_name,''),COALESCE(notes,'') SELECT id,template_id,title,task_period,month,day,year_offset,COALESCE(target_name,''),COALESCE(notes,'')
FROM custom_tasks FROM custom_tasks
ORDER BY year_offset, month, day, title`) ORDER BY task_period, day, title`)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -283,9 +297,11 @@ func (a *App) listCustomTasks() ([]CustomTask, error) {
var out []CustomTask var out []CustomTask
for rows.Next() { for rows.Next() {
var t CustomTask var t CustomTask
if err := rows.Scan(&t.ID, &t.TemplateID, &t.Title, &t.Month, &t.Day, &t.YearOffset, &t.TargetName, &t.Notes); err != nil { if err := rows.Scan(&t.ID, &t.TemplateID, &t.Title, &t.TaskPeriod, &t.Month, &t.Day, &t.YearOffset, &t.TargetName, &t.Notes); err != nil {
return nil, err return nil, err
} }
t.Month = periodMonth(t.TaskPeriod)
t.Cycle = periodCycle(t.TaskPeriod)
out = append(out, t) out = append(out, t)
} }
return out, rows.Err() return out, rows.Err()

View File

@@ -90,6 +90,20 @@ func monthOptions() []MonthOption {
return out return out
} }
func cycleOffsetOptions(maxOffset int) []CycleOption {
if maxOffset < 0 {
maxOffset = 0
}
out := make([]CycleOption, 0, maxOffset+1)
for i := 0; i <= maxOffset; i++ {
out = append(out, CycleOption{
Value: i,
Label: fmt.Sprintf("+%d Zyklus", i),
})
}
return out
}
func monthInWindow(month, start, end int) bool { func monthInWindow(month, start, end int) bool {
if start <= end { if start <= end {
return month >= start && month <= end return month >= start && month <= end
@@ -105,6 +119,14 @@ func wrapMonth(v int) int {
return m return m
} }
func periodMonth(period int) int {
return (period % 12) + 1
}
func periodCycle(period int) int {
return period / 12
}
func validateCropInput(name string, start, end, grow, regrowCycles int) error { func validateCropInput(name string, start, end, grow, regrowCycles int) error {
if name == "" { if name == "" {
return errors.New("Name der Feldfrucht fehlt") return errors.New("Name der Feldfrucht fehlt")

View File

@@ -46,12 +46,13 @@ func (a *App) handleSetCurrentTime(w http.ResponseWriter, r *http.Request) {
return return
} }
month := mustInt(r.FormValue("current_month"), 1) month := mustInt(r.FormValue("current_month"), 1)
cycle := mustInt(r.FormValue("current_cycle"), settings.CurrentCycle)
day := mustInt(r.FormValue("current_day"), 1) day := mustInt(r.FormValue("current_day"), 1)
if month < 1 || month > 12 || day < 1 || day > settings.DaysPerMonth { if month < 1 || month > 12 || cycle < 0 || day < 1 || day > settings.DaysPerMonth {
redirectWithMessage(w, r, "/", "error", "Ingame-Zeit ungültig") redirectWithMessage(w, r, "/", "error", "Ingame-Zeit ungültig")
return return
} }
if _, err := a.db.Exec(`UPDATE settings SET current_month=?, current_day=? WHERE id=1`, month, day); err != nil { if _, err := a.db.Exec(`UPDATE settings SET current_month=?, current_cycle=?, current_day=? WHERE id=1`, month, cycle, day); err != nil {
redirectWithMessage(w, r, "/", "error", "Ingame-Zeit nicht gespeichert") redirectWithMessage(w, r, "/", "error", "Ingame-Zeit nicht gespeichert")
return return
} }
@@ -369,6 +370,7 @@ func (a *App) handleCreatePlan(w http.ResponseWriter, r *http.Request) {
targetRef := strings.TrimSpace(r.FormValue("target_ref")) targetRef := strings.TrimSpace(r.FormValue("target_ref"))
cropID := mustInt64(r.FormValue("crop_id"), 0) cropID := mustInt64(r.FormValue("crop_id"), 0)
startMonth := mustInt(r.FormValue("start_month"), 1) startMonth := mustInt(r.FormValue("start_month"), 1)
cycleOffset := mustInt(r.FormValue("cycle_offset"), 0)
startDay := mustInt(r.FormValue("start_day"), 1) startDay := mustInt(r.FormValue("start_day"), 1)
notes := strings.TrimSpace(r.FormValue("notes")) notes := strings.TrimSpace(r.FormValue("notes"))
if targetRef == "" || cropID <= 0 { if targetRef == "" || cropID <= 0 {
@@ -381,7 +383,7 @@ func (a *App) handleCreatePlan(w http.ResponseWriter, r *http.Request) {
redirectWithMessage(w, r, "/planning", "error", "Einstellungen nicht lesbar") redirectWithMessage(w, r, "/planning", "error", "Einstellungen nicht lesbar")
return return
} }
if startMonth < 1 || startMonth > 12 || startDay < 1 || startDay > settings.DaysPerMonth { if startMonth < 1 || startMonth > 12 || cycleOffset < 0 || cycleOffset > 20 || startDay < 1 || startDay > settings.DaysPerMonth {
redirectWithMessage(w, r, "/planning", "error", "Startdatum ungültig") redirectWithMessage(w, r, "/planning", "error", "Startdatum ungültig")
return return
} }
@@ -404,11 +406,13 @@ func (a *App) handleCreatePlan(w http.ResponseWriter, r *http.Request) {
return return
} }
harvestMonth := wrapMonth(startMonth + c.GrowMonths) startPeriod := ((settings.CurrentCycle + cycleOffset) * 12) + (startMonth - 1)
harvestPeriod := startPeriod + c.GrowMonths
harvestMonth := periodMonth(harvestPeriod)
harvestDay := startDay harvestDay := startDay
_, err = a.db.Exec( _, 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 (?,?,?,?,?,?,?,?,?)`, `INSERT INTO plans(field_id,target_ref,target_name,crop_id,start_period,start_month,start_day,harvest_period,harvest_month,harvest_day,notes) VALUES (?,?,?,?,?,?,?,?,?,?,?)`,
fieldID, targetRef, targetName, cropID, startMonth, startDay, harvestMonth, harvestDay, notes, fieldID, targetRef, targetName, cropID, startPeriod, startMonth, startDay, harvestPeriod, harvestMonth, harvestDay, notes,
) )
if err != nil { if err != nil {
redirectWithMessage(w, r, "/planning", "error", "Plan nicht gespeichert") redirectWithMessage(w, r, "/planning", "error", "Plan nicht gespeichert")
@@ -508,7 +512,7 @@ func (a *App) handleCreateCustomTask(w http.ResponseWriter, r *http.Request) {
title := strings.TrimSpace(r.FormValue("title")) title := strings.TrimSpace(r.FormValue("title"))
month := mustInt(r.FormValue("month"), 0) month := mustInt(r.FormValue("month"), 0)
day := mustInt(r.FormValue("day"), 0) day := mustInt(r.FormValue("day"), 0)
yearOffset := mustInt(r.FormValue("year_offset"), 0) cycleOffset := mustInt(r.FormValue("cycle_offset"), 0)
targetName := strings.TrimSpace(r.FormValue("target_name")) targetName := strings.TrimSpace(r.FormValue("target_name"))
notes := strings.TrimSpace(r.FormValue("notes")) notes := strings.TrimSpace(r.FormValue("notes"))
settings, err := a.getSettings() settings, err := a.getSettings()
@@ -516,7 +520,7 @@ func (a *App) handleCreateCustomTask(w http.ResponseWriter, r *http.Request) {
redirectWithMessage(w, r, "/planning", "error", "Einstellungen nicht lesbar") redirectWithMessage(w, r, "/planning", "error", "Einstellungen nicht lesbar")
return return
} }
if month < 1 || month > 12 || day < 1 || day > settings.DaysPerMonth || yearOffset < 0 || yearOffset > 4 { if month < 1 || month > 12 || day < 1 || day > settings.DaysPerMonth || cycleOffset < 0 || cycleOffset > 20 {
redirectWithMessage(w, r, "/planning", "error", "Aufgaben-Datum ungültig") redirectWithMessage(w, r, "/planning", "error", "Aufgaben-Datum ungültig")
return return
} }
@@ -531,8 +535,9 @@ func (a *App) handleCreateCustomTask(w http.ResponseWriter, r *http.Request) {
return return
} }
if _, err = a.db.Exec(`INSERT INTO custom_tasks(template_id,title,month,day,year_offset,target_name,notes) VALUES (?,?,?,?,?,?,?)`, taskPeriod := ((settings.CurrentCycle + cycleOffset) * 12) + (month - 1)
nullInt64(templateID), title, month, day, yearOffset, targetName, notes); err != nil { if _, err = a.db.Exec(`INSERT INTO custom_tasks(template_id,title,task_period,month,day,year_offset,target_name,notes) VALUES (?,?,?,?,?,?,?,?)`,
nullInt64(templateID), title, taskPeriod, month, day, cycleOffset, targetName, notes); err != nil {
redirectWithMessage(w, r, "/planning", "error", "Aufgabe konnte nicht gespeichert werden") redirectWithMessage(w, r, "/planning", "error", "Aufgabe konnte nicht gespeichert werden")
return return
} }

View File

@@ -64,8 +64,8 @@ func (a *App) handleDashboard(w http.ResponseWriter, r *http.Request) {
}, },
Settings: settings, Settings: settings,
CurrentMonth: monthNames[settings.CurrentMonth-1], CurrentMonth: monthNames[settings.CurrentMonth-1],
TodayTasks: buildTasksForDay(plans, crops, products, stepMap, customTasks, doneMap, settings.CurrentMonth, settings.CurrentDay), TodayTasks: buildTasksForDay(plans, crops, products, stepMap, customTasks, doneMap, (settings.CurrentCycle*12)+(settings.CurrentMonth-1), settings.CurrentDay),
Calendar: buildCalendar(plans, crops, products, stepMap, customTasks, doneMap, settings.CurrentMonth, settings.CurrentDay, settings.DaysPerMonth, 14), Calendar: buildCalendar(plans, crops, products, stepMap, customTasks, doneMap, (settings.CurrentCycle*12)+(settings.CurrentMonth-1), settings.CurrentDay, settings.DaysPerMonth, 14),
PlanningCount: len(plans), PlanningCount: len(plans),
} }
data.TodayGroups = groupTasksByField(data.TodayTasks) data.TodayGroups = groupTasksByField(data.TodayTasks)
@@ -170,6 +170,7 @@ func (a *App) handlePlanningPage(w http.ResponseWriter, r *http.Request) {
}, },
Settings: settings, Settings: settings,
Months: monthOptions(), Months: monthOptions(),
CycleOffsets: cycleOffsetOptions(8),
Crops: crops, Crops: crops,
Plans: plans, Plans: plans,
PlanningTargets: buildPlanningTargets(fields), PlanningTargets: buildPlanningTargets(fields),

View File

@@ -1,4 +1,4 @@
package main package main
import ( import (
"database/sql" "database/sql"
@@ -99,3 +99,4 @@ func withLogging(next http.Handler) http.Handler {
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start).Round(time.Millisecond)) log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start).Round(time.Millisecond))
}) })
} }

View File

@@ -5,6 +5,7 @@ import "database/sql"
type Settings struct { type Settings struct {
DaysPerMonth int DaysPerMonth int
CurrentMonth int CurrentMonth int
CurrentCycle int
CurrentDay int CurrentDay int
} }
@@ -48,10 +49,14 @@ type Plan struct {
CropID int64 CropID int64
CropName string CropName string
GrowMonths int GrowMonths int
StartPeriod int
StartMonth int StartMonth int
StartDay int StartDay int
StartCycle int
HarvestPeriod int
HarvestMonth int HarvestMonth int
HarvestDay int HarvestDay int
HarvestCycle int
Notes string Notes string
RegrowEnabled bool RegrowEnabled bool
RegrowCycles int RegrowCycles int
@@ -75,9 +80,11 @@ type CustomTask struct {
ID int64 ID int64
TemplateID sql.NullInt64 TemplateID sql.NullInt64
Title string Title string
TaskPeriod int
Month int Month int
Day int Day int
YearOffset int YearOffset int
Cycle int
TargetName string TargetName string
Notes string Notes string
} }
@@ -104,6 +111,11 @@ type MonthOption struct {
Label string Label string
} }
type CycleOption struct {
Value int
Label string
}
type CalendarDay struct { type CalendarDay struct {
Day int Day int
Tasks []Task Tasks []Task
@@ -156,6 +168,7 @@ type PlanningPage struct {
BasePage BasePage
Settings Settings Settings Settings
Months []MonthOption Months []MonthOption
CycleOffsets []CycleOption
Crops []Crop Crops []Crop
Plans []Plan Plans []Plan
PlanningTargets []PlanningTarget PlanningTargets []PlanningTarget

View File

@@ -1,4 +1,4 @@
<!doctype html> <!doctype html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@@ -24,7 +24,7 @@
<main class="layout"> <main class="layout">
<section class="card"> <section class="card">
<h2>Aktuelle Ingame-Zeit</h2> <h2>Aktuelle Ingame-Zeit</h2>
<p><strong>{{.CurrentMonth}} Tag {{.Settings.CurrentDay}}</strong> bei {{.Settings.DaysPerMonth}} Tagen pro Monat.</p> <p><strong>{{.CurrentMonth}} Tag {{.Settings.CurrentDay}}</strong> im Zyklus {{.Settings.CurrentCycle}} bei {{.Settings.DaysPerMonth}} Tagen pro Monat.</p>
<p>{{.PlanningCount}} Plan-Einträge insgesamt.</p> <p>{{.PlanningCount}} Plan-Einträge insgesamt.</p>
<form method="post" action="/settings/time" class="grid"> <form method="post" action="/settings/time" class="grid">
<label>Monat <label>Monat
@@ -43,6 +43,9 @@
<option value="12" {{if eq .Settings.CurrentMonth 12}}selected{{end}}>Dezember</option> <option value="12" {{if eq .Settings.CurrentMonth 12}}selected{{end}}>Dezember</option>
</select> </select>
</label> </label>
<label>Zyklus
<input type="number" name="current_cycle" min="0" value="{{.Settings.CurrentCycle}}">
</label>
<label>Tag <label>Tag
<input type="number" name="current_day" min="1" max="{{.Settings.DaysPerMonth}}" value="{{.Settings.CurrentDay}}"> <input type="number" name="current_day" min="1" max="{{.Settings.DaysPerMonth}}" value="{{.Settings.CurrentDay}}">
</label> </label>

View File

@@ -1,4 +1,4 @@
<!doctype html> <!doctype html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@@ -44,6 +44,13 @@
{{end}} {{end}}
</select> </select>
</label> </label>
<label>Zyklus-Offset
<select name="cycle_offset" required>
{{range .CycleOffsets}}
<option value="{{.Value}}">{{.Label}}</option>
{{end}}
</select>
</label>
<label>Starttag <label>Starttag
<input type="number" name="start_day" min="1" max="{{.Settings.DaysPerMonth}}" value="{{.Settings.CurrentDay}}" required> <input type="number" name="start_day" min="1" max="{{.Settings.DaysPerMonth}}" value="{{.Settings.CurrentDay}}" required>
</label> </label>
@@ -88,8 +95,12 @@
<label>Tag <label>Tag
<input type="number" name="day" min="1" max="{{.Settings.DaysPerMonth}}" value="{{.Settings.CurrentDay}}"> <input type="number" name="day" min="1" max="{{.Settings.DaysPerMonth}}" value="{{.Settings.CurrentDay}}">
</label> </label>
<label>Jahr-Offset <label>Zyklus-Offset
<input type="number" name="year_offset" min="0" max="4" value="0"> <select name="cycle_offset">
{{range .CycleOffsets}}
<option value="{{.Value}}">{{.Label}}</option>
{{end}}
</select>
</label> </label>
<label>Ziel (optional) <label>Ziel (optional)
<input type="text" name="target_name" maxlength="120" placeholder="z.B. Hof oder Feld 1+2"> <input type="text" name="target_name" maxlength="120" placeholder="z.B. Hof oder Feld 1+2">
@@ -112,8 +123,8 @@
<tr> <tr>
<td>{{.TargetName}}</td> <td>{{.TargetName}}</td>
<td>{{.CropName}}</td> <td>{{.CropName}}</td>
<td>Monat {{.StartMonth}} Tag {{.StartDay}}</td> <td>Monat {{.StartMonth}} Tag {{.StartDay}} (Zyklus {{.StartCycle}})</td>
<td>Monat {{.HarvestMonth}} Tag {{.HarvestDay}}</td> <td>Monat {{.HarvestMonth}} Tag {{.HarvestDay}} (Zyklus {{.HarvestCycle}})</td>
<td>{{.Notes}}</td> <td>{{.Notes}}</td>
<td> <td>
<form method="post" action="/plans/delete"> <form method="post" action="/plans/delete">
@@ -142,7 +153,7 @@
<tr> <tr>
<td>{{.Title}}</td> <td>{{.Title}}</td>
<td>{{if .TargetName}}{{.TargetName}}{{else}}Allgemein{{end}}</td> <td>{{if .TargetName}}{{.TargetName}}{{else}}Allgemein{{end}}</td>
<td>Monat {{.Month}} Tag {{.Day}} (Jahr +{{.YearOffset}})</td> <td>Monat {{.Month}} Tag {{.Day}} (Zyklus {{.Cycle}})</td>
<td>{{.Notes}}</td> <td>{{.Notes}}</td>
<td> <td>
<form method="post" action="/custom-tasks/delete"> <form method="post" action="/custom-tasks/delete">