feat(automation): suggest follow-up checks after treatments and feedings
Two new rules, "Kontrolle nach Behandlung" and "Kontrolle nach Fütterung", create a check task when a treatment or feeding is recorded. The due date counts from the measure itself (end of treatment, else its date; feeding date) plus the configured number of days, and the task names the product and date. At most one open check per colony and rule. Both rules ship switched off and without an interval: the right timing depends on the product (package leaflet) and season, so the beekeeper sets it. Every task-creating rule can now carry a "due after N days" setting in the automation page. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
888edf7e1d
commit
5cf4e87ca3
13 files changed
+318
-11
No files matched your search
@@ -25,6 +25,8 @@ const TRIGGER_LABELS: Record<AutomationTriggerType, string> = {
|
||||
STOCK_ITEM_LOW: "Lagerartikel unter Mindestbestand",
|
||||
HONEY_BATCH_NUMBER_FORMAT: "Chargennummer-Format (Honigchargen)",
|
||||
BOTTLING_LOT_NUMBER_FORMAT: "Los-/Chargennummer-Format (Abfüllungen)",
|
||||
TREATMENT_FOLLOW_UP: "Kontrolle nach Behandlung",
|
||||
FEEDING_FOLLOW_UP: "Kontrolle nach Fütterung",
|
||||
};
|
||||
|
||||
const TRIGGER_DESCRIPTIONS: Record<AutomationTriggerType, string> = {
|
||||
@@ -44,6 +46,10 @@ const TRIGGER_DESCRIPTIONS: Record<AutomationTriggerType, string> = {
|
||||
"Wenn aktiv, wird die Chargennummer neuer Honigchargen automatisch nach diesem Format erzeugt, statt sie manuell einzugeben.",
|
||||
BOTTLING_LOT_NUMBER_FORMAT:
|
||||
"Wenn aktiv, wird die Los-/Chargennummer neuer Abfüllungen automatisch nach diesem Format erzeugt, statt sie manuell einzugeben.",
|
||||
TREATMENT_FOLLOW_UP:
|
||||
"Nach jeder erfassten Behandlung eine Kontrollaufgabe vorschlagen, fällig die eingestellten Tage nach Behandlungsende. Die passende Frist hängt vom Mittel ab (Packungsbeilage) – deshalb ab Werk aus und ohne voreingestellte Frist.",
|
||||
FEEDING_FOLLOW_UP:
|
||||
"Nach jeder erfassten Fütterung eine Kontrollaufgabe vorschlagen, fällig die eingestellten Tage nach der Fütterung. Ab Werk aus und ohne voreingestellte Frist.",
|
||||
};
|
||||
|
||||
const NUMBER_FORMAT_TRIGGER_TYPES: AutomationTriggerType[] = [
|
||||
@@ -60,6 +66,11 @@ function ruleFormat(rule: AutomationRule): string {
|
||||
return config?.format ?? "";
|
||||
}
|
||||
|
||||
function ruleDueInDays(rule: AutomationRule): string {
|
||||
const config = rule.config as { dueInDays?: number } | null;
|
||||
return config?.dueInDays === undefined ? "" : String(config.dueInDays);
|
||||
}
|
||||
|
||||
function ruleThreshold(rule: AutomationRule): string {
|
||||
const config = rule.config as { thresholdMitesPerDay?: number } | null;
|
||||
return String(config?.thresholdMitesPerDay ?? "");
|
||||
@@ -70,6 +81,7 @@ function RuleCard({ rule }: { rule: AutomationRule }) {
|
||||
const [actionTemplate, setActionTemplate] = useState(rule.actionTemplate ?? "");
|
||||
const [threshold, setThreshold] = useState(ruleThreshold(rule));
|
||||
const [format, setFormat] = useState(ruleFormat(rule));
|
||||
const [dueInDays, setDueInDays] = useState(ruleDueInDays(rule));
|
||||
const numberFormatRule = isNumberFormatRule(rule);
|
||||
|
||||
const mutation = useMutation({
|
||||
@@ -96,13 +108,17 @@ function RuleCard({ rule }: { rule: AutomationRule }) {
|
||||
mutation.mutate({ config: { format } });
|
||||
return;
|
||||
}
|
||||
const input: { config?: Record<string, unknown>; actionTemplate?: string } = {
|
||||
actionTemplate: actionTemplate || undefined,
|
||||
};
|
||||
if (rule.triggerType === "VARROA_THRESHOLD") {
|
||||
input.config = { thresholdMitesPerDay: Number(threshold) || 10 };
|
||||
// Empty = task without a due date; otherwise whole days, 0-365.
|
||||
const days = dueInDays.trim() === "" ? undefined : Math.round(Number(dueInDays));
|
||||
if (days !== undefined && (!Number.isFinite(days) || days < 0 || days > 365)) {
|
||||
toast.error("Frist bitte als ganze Tage zwischen 0 und 365 angeben – oder leer lassen.");
|
||||
return;
|
||||
}
|
||||
mutation.mutate(input);
|
||||
const config: Record<string, unknown> = { dueInDays: days };
|
||||
if (rule.triggerType === "VARROA_THRESHOLD") {
|
||||
config.thresholdMitesPerDay = Number(threshold) || 10;
|
||||
}
|
||||
mutation.mutate({ actionTemplate: actionTemplate || undefined, config });
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -150,6 +166,21 @@ function RuleCard({ rule }: { rule: AutomationRule }) {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!numberFormatRule ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor={`due-${rule.id}`}>Fällig nach (Tagen)</Label>
|
||||
<Input
|
||||
id={`due-${rule.id}`}
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
min={0}
|
||||
max={365}
|
||||
placeholder="leer = ohne Termin"
|
||||
value={dueInDays}
|
||||
onChange={(event) => setDueInDays(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{rule.triggerType === "VARROA_THRESHOLD" ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor={`threshold-${rule.id}`}>Schwellenwert (Milben/Tag)</Label>
|
||||
|
||||
@@ -6,7 +6,9 @@ export type AutomationTriggerType =
|
||||
| "COLONY_HEALTH_CRITICAL"
|
||||
| "STOCK_ITEM_LOW"
|
||||
| "HONEY_BATCH_NUMBER_FORMAT"
|
||||
| "BOTTLING_LOT_NUMBER_FORMAT";
|
||||
| "BOTTLING_LOT_NUMBER_FORMAT"
|
||||
| "TREATMENT_FOLLOW_UP"
|
||||
| "FEEDING_FOLLOW_UP";
|
||||
|
||||
export interface AutomationRule {
|
||||
id: string;
|
||||
|
||||
Reference in new issue
Block a user