270 lines
6.9 KiB
Go
270 lines
6.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
func buildTasksForDay(plans []Plan, crops []Crop, products []Product, stepMap map[int64][]CropStep, customTasks []CustomTask, doneMap map[string]bool, month, day int) []Task {
|
|
tasks := collectTasksForDate(plans, crops, products, stepMap, customTasks, month, day, 0)
|
|
applyCompletion(tasks, doneMap)
|
|
sortTasks(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 {
|
|
var out []CalendarMonth
|
|
for offset := 0; offset < spanMonths; offset++ {
|
|
month := wrapMonth(startMonth + offset)
|
|
yearOffset := (startMonth - 1 + offset) / 12
|
|
label := monthNames[month-1]
|
|
if yearOffset > 0 {
|
|
label = fmt.Sprintf("%s (+%d Jahr)", label, yearOffset)
|
|
}
|
|
|
|
fromDay := 1
|
|
if offset == 0 {
|
|
fromDay = startDay
|
|
}
|
|
|
|
var days []CalendarDay
|
|
for d := fromDay; d <= daysPerMonth; d++ {
|
|
items := collectTasksForDate(plans, crops, products, stepMap, customTasks, month, d, yearOffset)
|
|
applyCompletion(items, doneMap)
|
|
sortTasks(items)
|
|
days = append(days, CalendarDay{Day: d, Tasks: items, Groups: groupTasksByField(items)})
|
|
}
|
|
out = append(out, CalendarMonth{
|
|
Offset: offset,
|
|
Month: month,
|
|
Label: label,
|
|
YearOffset: yearOffset,
|
|
Days: days,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func groupTasksByField(tasks []Task) []FieldTaskGroup {
|
|
if len(tasks) == 0 {
|
|
return nil
|
|
}
|
|
order := make([]string, 0, len(tasks))
|
|
grouped := make(map[string][]Task)
|
|
for _, t := range tasks {
|
|
if _, ok := grouped[t.Field]; !ok {
|
|
order = append(order, t.Field)
|
|
}
|
|
grouped[t.Field] = append(grouped[t.Field], t)
|
|
}
|
|
out := make([]FieldTaskGroup, 0, len(order))
|
|
for _, field := range order {
|
|
out = append(out, FieldTaskGroup{Field: field, Tasks: grouped[field]})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func collectTasksForDate(plans []Plan, crops []Crop, products []Product, stepMap map[int64][]CropStep, customTasks []CustomTask, month, day, yearOffset int) []Task {
|
|
var tasks []Task
|
|
|
|
for _, p := range plans {
|
|
tasks = append(tasks, expandPlanDayTasks(p, stepMap[p.CropID], month, day, yearOffset)...)
|
|
}
|
|
if day == 1 {
|
|
for _, c := range crops {
|
|
if c.BestSaleMonth == month {
|
|
tasks = append(tasks, Task{
|
|
UID: fmt.Sprintf("sale:crop:%d", c.ID),
|
|
Type: "Verkauf",
|
|
Field: "Markt",
|
|
Message: fmt.Sprintf("Bestpreis Feldfrucht: %s", c.Name),
|
|
Month: month,
|
|
Day: day,
|
|
YearOffset: yearOffset,
|
|
SortOrder: 35,
|
|
})
|
|
}
|
|
}
|
|
for _, p := range products {
|
|
if p.BestSaleMonth == month {
|
|
tasks = append(tasks, Task{
|
|
UID: fmt.Sprintf("sale:product:%d", p.ID),
|
|
Type: "Verkauf",
|
|
Field: "Markt",
|
|
Message: fmt.Sprintf("Bestpreis Produktionsgut: %s", p.Name),
|
|
Month: month,
|
|
Day: day,
|
|
YearOffset: yearOffset,
|
|
SortOrder: 36,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
for _, c := range customTasks {
|
|
if c.Month == month && c.Day == day && c.YearOffset == yearOffset {
|
|
field := c.TargetName
|
|
if strings.TrimSpace(field) == "" {
|
|
field = "Allgemein"
|
|
}
|
|
msg := c.Title
|
|
if strings.TrimSpace(c.Notes) != "" {
|
|
msg = fmt.Sprintf("%s (Notiz: %s)", msg, c.Notes)
|
|
}
|
|
tasks = append(tasks, Task{
|
|
UID: fmt.Sprintf("custom:%d", c.ID),
|
|
Type: "Aufgabe",
|
|
Field: field,
|
|
Message: msg,
|
|
Month: month,
|
|
Day: day,
|
|
YearOffset: yearOffset,
|
|
SortOrder: 40,
|
|
})
|
|
}
|
|
}
|
|
|
|
return tasks
|
|
}
|
|
|
|
func expandPlanDayTasks(p Plan, steps []CropStep, month, day, yearOffset int) []Task {
|
|
field := p.TargetName
|
|
if strings.TrimSpace(field) == "" {
|
|
field = "Unbekanntes Feld"
|
|
}
|
|
|
|
var out []Task
|
|
baseHarvest := p.HarvestMonth
|
|
|
|
if p.StartMonth == month && p.StartDay == day && yearOffset == 0 {
|
|
out = append(out, Task{
|
|
UID: fmt.Sprintf("plan:%d:sow:0", p.ID),
|
|
Type: "Aussaat",
|
|
Field: field,
|
|
Message: withOptionalNote(fmt.Sprintf("Aussaat %s", p.CropName), p.Notes),
|
|
Month: month,
|
|
Day: day,
|
|
YearOffset: yearOffset,
|
|
SortOrder: 10,
|
|
})
|
|
}
|
|
|
|
harvestMonths := []int{baseHarvest}
|
|
if p.RegrowEnabled {
|
|
maxExtra := p.RegrowCycles
|
|
if maxExtra == 0 {
|
|
maxExtra = 24
|
|
}
|
|
for i := 1; i <= maxExtra; i++ {
|
|
harvestMonths = append(harvestMonths, wrapMonth(baseHarvest+(i*p.HarvestDistance())))
|
|
}
|
|
}
|
|
harvestMonths = uniqueMonths(harvestMonths)
|
|
|
|
for idx, hm := range harvestMonths {
|
|
if hm == month && p.HarvestDay == day {
|
|
uid := fmt.Sprintf("plan:%d:harvest:%d", p.ID, idx)
|
|
out = append(out, Task{
|
|
UID: uid,
|
|
Type: "Ernte",
|
|
Field: field,
|
|
Message: withOptionalNote(fmt.Sprintf("Ernte %s", p.CropName), p.Notes),
|
|
Month: month,
|
|
Day: day,
|
|
YearOffset: yearOffset,
|
|
SortOrder: 20,
|
|
})
|
|
}
|
|
}
|
|
|
|
for _, s := range steps {
|
|
switch s.Phase {
|
|
case "pre":
|
|
taskMonth := wrapMonth(p.StartMonth - s.MonthOffset)
|
|
if taskMonth == month && p.StartDay == day && yearOffset == 0 {
|
|
out = append(out, Task{
|
|
UID: fmt.Sprintf("plan:%d:pre:%d", p.ID, s.ID),
|
|
Type: "Vorbereitung",
|
|
Field: field,
|
|
Message: s.Title,
|
|
Month: month,
|
|
Day: day,
|
|
YearOffset: yearOffset,
|
|
SortOrder: 5,
|
|
})
|
|
}
|
|
case "post":
|
|
for idx, hm := range harvestMonths {
|
|
taskMonth := wrapMonth(hm + s.MonthOffset)
|
|
if taskMonth == month && p.HarvestDay == day {
|
|
out = append(out, Task{
|
|
UID: fmt.Sprintf("plan:%d:post:%d:%d", p.ID, s.ID, idx),
|
|
Type: "Nachbereitung",
|
|
Field: field,
|
|
Message: s.Title,
|
|
Month: month,
|
|
Day: day,
|
|
YearOffset: yearOffset,
|
|
SortOrder: 30,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func applyCompletion(tasks []Task, doneMap map[string]bool) {
|
|
for i := range tasks {
|
|
k := completionKey(tasks[i].UID, tasks[i].Month, tasks[i].Day, tasks[i].YearOffset)
|
|
tasks[i].Completed = doneMap[k]
|
|
}
|
|
}
|
|
|
|
func (p Plan) HarvestDistance() int {
|
|
if p.GrowMonths <= 0 {
|
|
return 1
|
|
}
|
|
return p.GrowMonths
|
|
}
|
|
|
|
func withOptionalNote(base, note string) string {
|
|
n := strings.TrimSpace(note)
|
|
if n == "" {
|
|
return base
|
|
}
|
|
return fmt.Sprintf("%s (Notiz: %s)", base, n)
|
|
}
|
|
|
|
func sortTasks(tasks []Task) {
|
|
sort.Slice(tasks, func(i, j int) bool {
|
|
if tasks[i].Completed != tasks[j].Completed {
|
|
return !tasks[i].Completed
|
|
}
|
|
if tasks[i].SortOrder == tasks[j].SortOrder {
|
|
if tasks[i].Field == tasks[j].Field {
|
|
return tasks[i].Message < tasks[j].Message
|
|
}
|
|
return tasks[i].Field < tasks[j].Field
|
|
}
|
|
return tasks[i].SortOrder < tasks[j].SortOrder
|
|
})
|
|
}
|
|
|
|
func uniqueMonths(values []int) []int {
|
|
seen := make(map[int]bool)
|
|
var out []int
|
|
for _, v := range values {
|
|
if !seen[v] {
|
|
seen[v] = true
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func completionKey(uid string, month, day, yearOffset int) string {
|
|
return fmt.Sprintf("%s|%d|%d|%d", uid, month, day, yearOffset)
|
|
}
|