package main import ( "errors" "fmt" "sort" "strconv" "strings" ) func buildPlanningTargets(fields []Field) []PlanningTarget { groupMap := make(map[string][]Field) var out []PlanningTarget for _, f := range fields { if !f.Owned { continue } if f.GroupKey == "" { out = append(out, PlanningTarget{Ref: fmt.Sprintf("f:%d", f.ID), Label: fieldLabel(f)}) continue } groupMap[f.GroupKey] = append(groupMap[f.GroupKey], f) } groupKeys := make([]string, 0, len(groupMap)) for k := range groupMap { groupKeys = append(groupKeys, k) } sort.Strings(groupKeys) for _, k := range groupKeys { members := groupMap[k] sort.Slice(members, func(i, j int) bool { return members[i].Number < members[j].Number }) name := members[0].GroupName if strings.TrimSpace(name) == "" { name = autoGroupName(members) } out = append(out, PlanningTarget{Ref: "g:" + k, Label: name}) } sort.Slice(out, func(i, j int) bool { return out[i].Label < out[j].Label }) return out } func buildFieldGroups(fields []Field) []FieldGroup { groupMap := make(map[string][]Field) for _, f := range fields { if strings.TrimSpace(f.GroupKey) == "" { continue } groupMap[f.GroupKey] = append(groupMap[f.GroupKey], f) } var out []FieldGroup for key, members := range groupMap { sort.Slice(members, func(i, j int) bool { return members[i].Number < members[j].Number }) nums := make([]string, 0, len(members)) for _, m := range members { nums = append(nums, strconv.Itoa(m.Number)) } name := members[0].GroupName if strings.TrimSpace(name) == "" { name = autoGroupName(members) } out = append(out, FieldGroup{Key: key, Name: name, Numbers: strings.Join(nums, "+")}) } sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name }) return out } func autoGroupName(fields []Field) string { sort.Slice(fields, func(i, j int) bool { return fields[i].Number < fields[j].Number }) parts := make([]string, 0, len(fields)) for _, f := range fields { parts = append(parts, strconv.Itoa(f.Number)) } return "Feld " + strings.Join(parts, "+") } func fieldLabel(f Field) string { base := fmt.Sprintf("Feld %d", f.Number) if strings.TrimSpace(f.Name) != "" { base += " (" + f.Name + ")" } return base } func monthOptions() []MonthOption { out := make([]MonthOption, 0, 12) for i, m := range monthNames { out = append(out, MonthOption{Value: i + 1, Label: m}) } return out } func monthInWindow(month, start, end int) bool { if start <= end { return month >= start && month <= end } return month >= start || month <= end } func wrapMonth(v int) int { m := v % 12 if m == 0 { return 12 } return m } func validateCropInput(name string, start, end, grow, regrowCycles int) error { if name == "" { return errors.New("Name der Feldfrucht fehlt") } if start < 1 || start > 12 || end < 1 || end > 12 { return errors.New("Aussaatmonat muss 1-12 sein") } if grow < 1 || grow > 24 { return errors.New("Wachstumsdauer muss 1-24 sein") } if regrowCycles < 0 || regrowCycles > 120 { return errors.New("Regrow-Zyklen muessen 0-120 sein") } return nil }