Fortlaufende Zyklus-Planung und Periodenlogik einführen
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user