Add sales-month planning for crops/products and fix today task label format
This commit is contained in:
@@ -6,14 +6,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildTasksForDay(plans []Plan, stepMap map[int64][]CropStep, customTasks []CustomTask, doneMap map[string]bool, month, day int) []Task {
|
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, stepMap, customTasks, month, day, 0)
|
tasks := collectTasksForDate(plans, crops, products, stepMap, customTasks, month, day, 0)
|
||||||
applyCompletion(tasks, doneMap)
|
applyCompletion(tasks, doneMap)
|
||||||
sortTasks(tasks)
|
sortTasks(tasks)
|
||||||
return tasks
|
return tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildCalendar(plans []Plan, 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, startMonth, startDay, daysPerMonth, spanMonths int) []CalendarMonth {
|
||||||
var out []CalendarMonth
|
var out []CalendarMonth
|
||||||
for offset := 0; offset < spanMonths; offset++ {
|
for offset := 0; offset < spanMonths; offset++ {
|
||||||
month := wrapMonth(startMonth + offset)
|
month := wrapMonth(startMonth + offset)
|
||||||
@@ -30,7 +30,7 @@ func buildCalendar(plans []Plan, stepMap map[int64][]CropStep, customTasks []Cus
|
|||||||
|
|
||||||
var days []CalendarDay
|
var days []CalendarDay
|
||||||
for d := fromDay; d <= daysPerMonth; d++ {
|
for d := fromDay; d <= daysPerMonth; d++ {
|
||||||
items := collectTasksForDate(plans, stepMap, customTasks, month, d, yearOffset)
|
items := collectTasksForDate(plans, crops, products, stepMap, customTasks, month, d, yearOffset)
|
||||||
applyCompletion(items, doneMap)
|
applyCompletion(items, doneMap)
|
||||||
sortTasks(items)
|
sortTasks(items)
|
||||||
days = append(days, CalendarDay{Day: d, Tasks: items})
|
days = append(days, CalendarDay{Day: d, Tasks: items})
|
||||||
@@ -46,12 +46,42 @@ func buildCalendar(plans []Plan, stepMap map[int64][]CropStep, customTasks []Cus
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectTasksForDate(plans []Plan, 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, month, day, yearOffset int) []Task {
|
||||||
var tasks []Task
|
var tasks []Task
|
||||||
|
|
||||||
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], 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 {
|
for _, c := range customTasks {
|
||||||
if c.Month == month && c.Day == day && c.YearOffset == yearOffset {
|
if c.Month == month && c.Day == day && c.YearOffset == yearOffset {
|
||||||
field := c.TargetName
|
field := c.TargetName
|
||||||
|
|||||||
@@ -38,12 +38,21 @@ func ensureSchema(db *sql.DB) error {
|
|||||||
sow_start_month TINYINT NOT NULL,
|
sow_start_month TINYINT NOT NULL,
|
||||||
sow_end_month TINYINT NOT NULL,
|
sow_end_month TINYINT NOT NULL,
|
||||||
grow_months TINYINT NOT NULL,
|
grow_months TINYINT NOT NULL,
|
||||||
|
best_sale_month TINYINT NOT NULL DEFAULT 0,
|
||||||
regrow_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
regrow_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
regrow_cycles INT NOT NULL DEFAULT 0
|
regrow_cycles INT NOT NULL DEFAULT 0
|
||||||
)`,
|
)`,
|
||||||
|
`ALTER TABLE crops ADD COLUMN IF NOT EXISTS best_sale_month TINYINT NOT NULL DEFAULT 0`,
|
||||||
`ALTER TABLE crops ADD COLUMN IF NOT EXISTS regrow_enabled TINYINT(1) NOT NULL DEFAULT 0`,
|
`ALTER TABLE crops ADD COLUMN IF NOT EXISTS regrow_enabled TINYINT(1) NOT NULL DEFAULT 0`,
|
||||||
`ALTER TABLE crops ADD COLUMN IF NOT EXISTS regrow_cycles INT NOT NULL DEFAULT 0`,
|
`ALTER TABLE crops ADD COLUMN IF NOT EXISTS regrow_cycles INT NOT NULL DEFAULT 0`,
|
||||||
`UPDATE crops SET regrow_cycles=0 WHERE regrow_cycles < 0`,
|
`UPDATE crops SET regrow_cycles=0 WHERE regrow_cycles < 0`,
|
||||||
|
`UPDATE crops SET best_sale_month=0 WHERE best_sale_month < 0 OR best_sale_month > 12`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS products(
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(120) NOT NULL UNIQUE,
|
||||||
|
best_sale_month TINYINT NOT NULL DEFAULT 0,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)`,
|
||||||
`CREATE TABLE IF NOT EXISTS crop_steps(
|
`CREATE TABLE IF NOT EXISTS crop_steps(
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
crop_id BIGINT NOT NULL,
|
crop_id BIGINT NOT NULL,
|
||||||
@@ -154,7 +163,7 @@ func (a *App) getFieldsByIDs(ids []int64) ([]Field, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) listCrops() ([]Crop, error) {
|
func (a *App) listCrops() ([]Crop, error) {
|
||||||
rows, err := a.db.Query(`SELECT id,name,sow_start_month,sow_end_month,grow_months,regrow_enabled,regrow_cycles FROM crops ORDER BY name`)
|
rows, err := a.db.Query(`SELECT id,name,sow_start_month,sow_end_month,grow_months,best_sale_month,regrow_enabled,regrow_cycles FROM crops ORDER BY name`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -162,7 +171,7 @@ func (a *App) listCrops() ([]Crop, error) {
|
|||||||
var out []Crop
|
var out []Crop
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var c Crop
|
var c Crop
|
||||||
if err := rows.Scan(&c.ID, &c.Name, &c.SowStartMonth, &c.SowEndMonth, &c.GrowMonths, &c.RegrowEnabled, &c.RegrowCycles); err != nil {
|
if err := rows.Scan(&c.ID, &c.Name, &c.SowStartMonth, &c.SowEndMonth, &c.GrowMonths, &c.BestSaleMonth, &c.RegrowEnabled, &c.RegrowCycles); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out = append(out, c)
|
out = append(out, c)
|
||||||
@@ -170,6 +179,24 @@ func (a *App) listCrops() ([]Crop, error) {
|
|||||||
return out, rows.Err()
|
return out, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) listProducts() ([]Product, error) {
|
||||||
|
rows, err := a.db.Query(`SELECT id,name,best_sale_month FROM products ORDER BY name`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var out []Product
|
||||||
|
for rows.Next() {
|
||||||
|
var p Product
|
||||||
|
if err := rows.Scan(&p.ID, &p.Name, &p.BestSaleMonth); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
return out, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
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_month,p.start_day,p.harvest_month,p.harvest_day,COALESCE(p.notes,''),COALESCE(c.regrow_enabled,0),COALESCE(c.regrow_cycles,0)
|
||||||
@@ -301,7 +328,7 @@ func seedCrops(db *sql.DB) error {
|
|||||||
{Name: "Baumwolle", SowStartMonth: 2, SowEndMonth: 3, GrowMonths: 8},
|
{Name: "Baumwolle", SowStartMonth: 2, SowEndMonth: 3, GrowMonths: 8},
|
||||||
}
|
}
|
||||||
for _, c := range items {
|
for _, c := range items {
|
||||||
if _, err := db.Exec(`INSERT INTO crops(name,sow_start_month,sow_end_month,grow_months,regrow_enabled,regrow_cycles) VALUES (?,?,?,?,0,0) ON DUPLICATE KEY UPDATE sow_start_month=VALUES(sow_start_month),sow_end_month=VALUES(sow_end_month),grow_months=VALUES(grow_months)`, c.Name, c.SowStartMonth, c.SowEndMonth, c.GrowMonths); err != nil {
|
if _, err := db.Exec(`INSERT INTO crops(name,sow_start_month,sow_end_month,grow_months,best_sale_month,regrow_enabled,regrow_cycles) VALUES (?,?,?,?,0,0,0) ON DUPLICATE KEY UPDATE sow_start_month=VALUES(sow_start_month),sow_end_month=VALUES(sow_end_month),grow_months=VALUES(grow_months)`, c.Name, c.SowStartMonth, c.SowEndMonth, c.GrowMonths); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,16 +210,21 @@ func (a *App) handleCreateCrop(w http.ResponseWriter, r *http.Request) {
|
|||||||
start := mustInt(r.FormValue("sow_start_month"), 0)
|
start := mustInt(r.FormValue("sow_start_month"), 0)
|
||||||
end := mustInt(r.FormValue("sow_end_month"), 0)
|
end := mustInt(r.FormValue("sow_end_month"), 0)
|
||||||
grow := mustInt(r.FormValue("grow_months"), 0)
|
grow := mustInt(r.FormValue("grow_months"), 0)
|
||||||
|
bestSaleMonth := mustInt(r.FormValue("best_sale_month"), 0)
|
||||||
regrowEnabled := r.FormValue("regrow_enabled") == "on"
|
regrowEnabled := r.FormValue("regrow_enabled") == "on"
|
||||||
regrowCycles := mustInt(r.FormValue("regrow_cycles"), 0)
|
regrowCycles := mustInt(r.FormValue("regrow_cycles"), 0)
|
||||||
if err := validateCropInput(name, start, end, grow, regrowCycles); err != nil {
|
if err := validateCropInput(name, start, end, grow, regrowCycles); err != nil {
|
||||||
redirectWithMessage(w, r, "/crops", "error", err.Error())
|
redirectWithMessage(w, r, "/crops", "error", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if bestSaleMonth < 0 || bestSaleMonth > 12 {
|
||||||
|
redirectWithMessage(w, r, "/crops", "error", "Bestpreis-Monat muss 0-12 sein")
|
||||||
|
return
|
||||||
|
}
|
||||||
if !regrowEnabled {
|
if !regrowEnabled {
|
||||||
regrowCycles = 0
|
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 {
|
if _, err := a.db.Exec(`INSERT INTO crops(name,sow_start_month,sow_end_month,grow_months,best_sale_month,regrow_enabled,regrow_cycles) VALUES (?,?,?,?,?,?,?)`, name, start, end, grow, bestSaleMonth, regrowEnabled, regrowCycles); err != nil {
|
||||||
redirectWithMessage(w, r, "/crops", "error", "Feldfrucht nicht angelegt")
|
redirectWithMessage(w, r, "/crops", "error", "Feldfrucht nicht angelegt")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -240,6 +245,7 @@ func (a *App) handleUpdateCrop(w http.ResponseWriter, r *http.Request) {
|
|||||||
start := mustInt(r.FormValue("sow_start_month"), 0)
|
start := mustInt(r.FormValue("sow_start_month"), 0)
|
||||||
end := mustInt(r.FormValue("sow_end_month"), 0)
|
end := mustInt(r.FormValue("sow_end_month"), 0)
|
||||||
grow := mustInt(r.FormValue("grow_months"), 0)
|
grow := mustInt(r.FormValue("grow_months"), 0)
|
||||||
|
bestSaleMonth := mustInt(r.FormValue("best_sale_month"), 0)
|
||||||
regrowEnabled := r.FormValue("regrow_enabled") == "on"
|
regrowEnabled := r.FormValue("regrow_enabled") == "on"
|
||||||
regrowCycles := mustInt(r.FormValue("regrow_cycles"), 0)
|
regrowCycles := mustInt(r.FormValue("regrow_cycles"), 0)
|
||||||
if id <= 0 {
|
if id <= 0 {
|
||||||
@@ -250,10 +256,14 @@ func (a *App) handleUpdateCrop(w http.ResponseWriter, r *http.Request) {
|
|||||||
redirectWithMessage(w, r, "/crops", "error", err.Error())
|
redirectWithMessage(w, r, "/crops", "error", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if bestSaleMonth < 0 || bestSaleMonth > 12 {
|
||||||
|
redirectWithMessage(w, r, "/crops", "error", "Bestpreis-Monat muss 0-12 sein")
|
||||||
|
return
|
||||||
|
}
|
||||||
if !regrowEnabled {
|
if !regrowEnabled {
|
||||||
regrowCycles = 0
|
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 {
|
if _, err := a.db.Exec(`UPDATE crops SET name=?,sow_start_month=?,sow_end_month=?,grow_months=?,best_sale_month=?,regrow_enabled=?,regrow_cycles=? WHERE id=?`, name, start, end, grow, bestSaleMonth, regrowEnabled, regrowCycles, id); err != nil {
|
||||||
redirectWithMessage(w, r, "/crops", "error", "Feldfrucht nicht aktualisiert")
|
redirectWithMessage(w, r, "/crops", "error", "Feldfrucht nicht aktualisiert")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -281,6 +291,72 @@ func (a *App) handleDeleteCrop(w http.ResponseWriter, r *http.Request) {
|
|||||||
redirectWithMessage(w, r, "/crops", "info", "Feldfrucht gelöscht")
|
redirectWithMessage(w, r, "/crops", "info", "Feldfrucht gelöscht")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) handleCreateProduct(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 ungültig")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := strings.TrimSpace(r.FormValue("name"))
|
||||||
|
bestSaleMonth := mustInt(r.FormValue("best_sale_month"), 0)
|
||||||
|
if name == "" || bestSaleMonth < 1 || bestSaleMonth > 12 {
|
||||||
|
redirectWithMessage(w, r, "/crops", "error", "Produktionsgut ungültig")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := a.db.Exec(`INSERT INTO products(name,best_sale_month) VALUES (?,?)`, name, bestSaleMonth); err != nil {
|
||||||
|
redirectWithMessage(w, r, "/crops", "error", "Produktionsgut nicht angelegt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
redirectWithMessage(w, r, "/crops", "info", "Produktionsgut angelegt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) handleUpdateProduct(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 ungültig")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := mustInt64(r.FormValue("id"), 0)
|
||||||
|
name := strings.TrimSpace(r.FormValue("name"))
|
||||||
|
bestSaleMonth := mustInt(r.FormValue("best_sale_month"), 0)
|
||||||
|
if id <= 0 || name == "" || bestSaleMonth < 1 || bestSaleMonth > 12 {
|
||||||
|
redirectWithMessage(w, r, "/crops", "error", "Produktionsgut ungültig")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := a.db.Exec(`UPDATE products SET name=?,best_sale_month=? WHERE id=?`, name, bestSaleMonth, id); err != nil {
|
||||||
|
redirectWithMessage(w, r, "/crops", "error", "Produktionsgut nicht aktualisiert")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
redirectWithMessage(w, r, "/crops", "info", "Produktionsgut aktualisiert")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) handleDeleteProduct(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 ungültig")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := mustInt64(r.FormValue("id"), 0)
|
||||||
|
if id <= 0 {
|
||||||
|
redirectWithMessage(w, r, "/crops", "error", "Produktionsgut-ID ungültig")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := a.db.Exec(`DELETE FROM products WHERE id=?`, id); err != nil {
|
||||||
|
redirectWithMessage(w, r, "/crops", "error", "Produktionsgut nicht gelöscht")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
redirectWithMessage(w, r, "/crops", "info", "Produktionsgut gelöscht")
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) handleCreatePlan(w http.ResponseWriter, r *http.Request) {
|
func (a *App) handleCreatePlan(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
|||||||
@@ -31,6 +31,16 @@ func (a *App) handleDashboard(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "plans read failed", http.StatusInternalServerError)
|
http.Error(w, "plans read failed", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
crops, err := a.listCrops()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "crops read failed", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
products, err := a.listProducts()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "products read failed", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
stepMap, err := a.listCropStepsMap()
|
stepMap, err := a.listCropStepsMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "steps read failed", http.StatusInternalServerError)
|
http.Error(w, "steps read failed", http.StatusInternalServerError)
|
||||||
@@ -54,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, stepMap, customTasks, doneMap, settings.CurrentMonth, settings.CurrentDay),
|
TodayTasks: buildTasksForDay(plans, crops, products, stepMap, customTasks, doneMap, settings.CurrentMonth, settings.CurrentDay),
|
||||||
Calendar: buildCalendar(plans, stepMap, customTasks, doneMap, settings.CurrentMonth, settings.CurrentDay, settings.DaysPerMonth, 14),
|
Calendar: buildCalendar(plans, crops, products, stepMap, customTasks, doneMap, settings.CurrentMonth, settings.CurrentDay, settings.DaysPerMonth, 14),
|
||||||
PlanningCount: len(plans),
|
PlanningCount: len(plans),
|
||||||
}
|
}
|
||||||
a.renderTemplate(w, "templates/dashboard.html", data)
|
a.renderTemplate(w, "templates/dashboard.html", data)
|
||||||
@@ -98,6 +108,11 @@ func (a *App) handleCropsPage(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "steps read failed", http.StatusInternalServerError)
|
http.Error(w, "steps read failed", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
products, err := a.listProducts()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "products read failed", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
data := CropsPage{
|
data := CropsPage{
|
||||||
BasePage: BasePage{
|
BasePage: BasePage{
|
||||||
ActivePath: "/crops",
|
ActivePath: "/crops",
|
||||||
@@ -106,6 +121,7 @@ func (a *App) handleCropsPage(w http.ResponseWriter, r *http.Request) {
|
|||||||
},
|
},
|
||||||
Crops: crops,
|
Crops: crops,
|
||||||
CropSteps: steps,
|
CropSteps: steps,
|
||||||
|
Products: products,
|
||||||
}
|
}
|
||||||
a.renderTemplate(w, "templates/crops.html", data)
|
a.renderTemplate(w, "templates/crops.html", data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ func main() {
|
|||||||
mux.HandleFunc("/crops/create", app.handleCreateCrop)
|
mux.HandleFunc("/crops/create", app.handleCreateCrop)
|
||||||
mux.HandleFunc("/crops/update", app.handleUpdateCrop)
|
mux.HandleFunc("/crops/update", app.handleUpdateCrop)
|
||||||
mux.HandleFunc("/crops/delete", app.handleDeleteCrop)
|
mux.HandleFunc("/crops/delete", app.handleDeleteCrop)
|
||||||
|
mux.HandleFunc("/products/create", app.handleCreateProduct)
|
||||||
|
mux.HandleFunc("/products/update", app.handleUpdateProduct)
|
||||||
|
mux.HandleFunc("/products/delete", app.handleDeleteProduct)
|
||||||
mux.HandleFunc("/crop-steps/create", app.handleCreateCropStep)
|
mux.HandleFunc("/crop-steps/create", app.handleCreateCropStep)
|
||||||
mux.HandleFunc("/crop-steps/delete", app.handleDeleteCropStep)
|
mux.HandleFunc("/crop-steps/delete", app.handleDeleteCropStep)
|
||||||
mux.HandleFunc("/task-templates/create", app.handleCreateTaskTemplate)
|
mux.HandleFunc("/task-templates/create", app.handleCreateTaskTemplate)
|
||||||
|
|||||||
@@ -29,10 +29,17 @@ type Crop struct {
|
|||||||
SowStartMonth int
|
SowStartMonth int
|
||||||
SowEndMonth int
|
SowEndMonth int
|
||||||
GrowMonths int
|
GrowMonths int
|
||||||
|
BestSaleMonth int
|
||||||
RegrowEnabled bool
|
RegrowEnabled bool
|
||||||
RegrowCycles int
|
RegrowCycles int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Product struct {
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
BestSaleMonth int
|
||||||
|
}
|
||||||
|
|
||||||
type Plan struct {
|
type Plan struct {
|
||||||
ID int64
|
ID int64
|
||||||
FieldID sql.NullInt64
|
FieldID sql.NullInt64
|
||||||
@@ -135,6 +142,7 @@ type CropsPage struct {
|
|||||||
BasePage
|
BasePage
|
||||||
Crops []Crop
|
Crops []Crop
|
||||||
CropSteps []CropStep
|
CropSteps []CropStep
|
||||||
|
Products []Product
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlanningPage struct {
|
type PlanningPage struct {
|
||||||
|
|||||||
@@ -25,6 +25,9 @@
|
|||||||
<label>Aussaat von Monat<input type="number" name="sow_start_month" min="1" max="12" required></label>
|
<label>Aussaat von Monat<input type="number" name="sow_start_month" min="1" max="12" required></label>
|
||||||
<label>Aussaat bis Monat<input type="number" name="sow_end_month" min="1" max="12" required></label>
|
<label>Aussaat bis Monat<input type="number" name="sow_end_month" min="1" max="12" required></label>
|
||||||
<label>Wachstum (Monate)<input type="number" name="grow_months" min="1" max="24" required></label>
|
<label>Wachstum (Monate)<input type="number" name="grow_months" min="1" max="24" required></label>
|
||||||
|
<label>Bestpreis-Monat (0=kein Hinweis)
|
||||||
|
<input type="number" name="best_sale_month" min="0" max="12" value="0">
|
||||||
|
</label>
|
||||||
<label class="check"><input type="checkbox" name="regrow_enabled"> Wächst nach Ernte erneut</label>
|
<label class="check"><input type="checkbox" name="regrow_enabled"> Wächst nach Ernte erneut</label>
|
||||||
<label>Zusatz-Ernten (0 = unendlich)
|
<label>Zusatz-Ernten (0 = unendlich)
|
||||||
<input type="number" name="regrow_cycles" min="0" max="120" value="0">
|
<input type="number" name="regrow_cycles" min="0" max="120" value="0">
|
||||||
@@ -37,7 +40,7 @@
|
|||||||
<h2>Bestehende Feldfrüchte</h2>
|
<h2>Bestehende Feldfrüchte</h2>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
<thead><tr><th>Name</th><th>Aussaat</th><th>Wachstum</th><th>Aktionen</th></tr></thead>
|
<thead><tr><th>Name</th><th>Aussaat</th><th>Wachstum</th><th>Bestpreis</th><th>Aktionen</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{if .Crops}}
|
{{if .Crops}}
|
||||||
{{range .Crops}}
|
{{range .Crops}}
|
||||||
@@ -54,6 +57,7 @@
|
|||||||
<label class="check mt-xs"><input type="checkbox" name="regrow_enabled" {{if .RegrowEnabled}}checked{{end}}> Regrow</label>
|
<label class="check mt-xs"><input type="checkbox" name="regrow_enabled" {{if .RegrowEnabled}}checked{{end}}> Regrow</label>
|
||||||
<input type="number" name="regrow_cycles" min="0" max="120" value="{{.RegrowCycles}}" title="0 = unendlich">
|
<input type="number" name="regrow_cycles" min="0" max="120" value="{{.RegrowCycles}}" title="0 = unendlich">
|
||||||
</td>
|
</td>
|
||||||
|
<td><input type="number" name="best_sale_month" min="0" max="12" value="{{.BestSaleMonth}}"></td>
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<button type="submit" class="btn-small">Speichern</button>
|
<button type="submit" class="btn-small">Speichern</button>
|
||||||
<button type="submit" formaction="/crops/delete" formnovalidate class="btn-small danger" onclick="return confirm('Feldfrucht wirklich löschen?')">Löschen</button>
|
<button type="submit" formaction="/crops/delete" formnovalidate class="btn-small danger" onclick="return confirm('Feldfrucht wirklich löschen?')">Löschen</button>
|
||||||
@@ -62,7 +66,44 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<tr><td colspan="4">Keine Feldfrüchte vorhanden.</td></tr>
|
<tr><td colspan="5">Keine Feldfrüchte vorhanden.</td></tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card full-width">
|
||||||
|
<h2>Produktionsgüter</h2>
|
||||||
|
<form method="post" action="/products/create" class="grid">
|
||||||
|
<label>Name
|
||||||
|
<input type="text" name="name" maxlength="120" required placeholder="z.B. Mehl">
|
||||||
|
</label>
|
||||||
|
<label>Bestpreis-Monat
|
||||||
|
<input type="number" name="best_sale_month" min="1" max="12" required>
|
||||||
|
</label>
|
||||||
|
<button type="submit">Produktionsgut anlegen</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="table-wrap mt">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Name</th><th>Bestpreis-Monat</th><th>Aktionen</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{{if .Products}}
|
||||||
|
{{range .Products}}
|
||||||
|
<tr>
|
||||||
|
<form method="post" action="/products/update">
|
||||||
|
<td><input type="hidden" name="id" value="{{.ID}}"><input type="text" name="name" value="{{.Name}}" maxlength="120" required></td>
|
||||||
|
<td><input type="number" name="best_sale_month" min="1" max="12" value="{{.BestSaleMonth}}" required></td>
|
||||||
|
<td class="actions">
|
||||||
|
<button type="submit" class="btn-small">Speichern</button>
|
||||||
|
<button type="submit" formaction="/products/delete" formnovalidate class="btn-small danger" onclick="return confirm('Produktionsgut wirklich löschen?')">Löschen</button>
|
||||||
|
</td>
|
||||||
|
</form>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
<tr><td colspan="3">Keine Produktionsgüter vorhanden.</td></tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<ul class="tasks">
|
<ul class="tasks">
|
||||||
{{range .TodayTasks}}
|
{{range .TodayTasks}}
|
||||||
<li class="{{if .Completed}}done-task{{end}}">
|
<li class="{{if .Completed}}done-task{{end}}">
|
||||||
<span><strong>{{.Type}}:</strong> {{.Message}}</span>
|
<span>{{.Field}}: {{.Message}}</span>
|
||||||
<form method="post" action="/tasks/toggle" class="inline-form">
|
<form method="post" action="/tasks/toggle" class="inline-form">
|
||||||
<input type="hidden" name="uid" value="{{.UID}}">
|
<input type="hidden" name="uid" value="{{.UID}}">
|
||||||
<input type="hidden" name="month" value="{{.Month}}">
|
<input type="hidden" name="month" value="{{.Month}}">
|
||||||
|
|||||||
Reference in New Issue
Block a user