feat: T4 waves 1-2 - rule configs, schema, suggestions core, season, rule forms
- Rule configs with typed parsers and registry; Berlin day helpers - Schema: T4 enums, Suggestion table, feed vocabulary (feedStatus as ColonyCondition with legacy text mapping), split queen source, varroa sample size; migration harness for data migrations - SuggestionsService: propose/refresh/resolve/obsolete/expire, accept/dismiss/reopen incl. bulk, lock order, concurrency e2e - SeasonService: phenology-based segments, inspection/varroa due dates, queen age; TimeEventsStep with JobPartialFailure - Frontend: per-rule settings forms with visible value sources Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
7097fcd97c
commit
d99a697739
116 files changed
+11239
-801
No files matched your search
@@ -0,0 +1,100 @@
|
||||
import { Prisma } from '../../../generated/prisma';
|
||||
import type { AutomationTriggerType } from '../../../generated/prisma';
|
||||
import { assertTransactionClient, type DbClient } from '../../prisma/db-client';
|
||||
import { lockRowForUpdate } from '../../prisma/row-lock';
|
||||
import { DEFAULT_RULES } from './default-rules';
|
||||
import { ruleConfigOf } from './rule-config.registry';
|
||||
import type { RuleConfigMap } from './rule-config.types';
|
||||
|
||||
export interface ActiveRule<K extends AutomationTriggerType> {
|
||||
/** null while no row exists yet: the built-in setting applies. */
|
||||
id: string | null;
|
||||
active: boolean;
|
||||
config: RuleConfigMap[K];
|
||||
actionTemplate: string | null;
|
||||
}
|
||||
|
||||
interface RuleRow {
|
||||
id: string;
|
||||
active: boolean;
|
||||
config: Prisma.JsonValue;
|
||||
actionTemplate: string | null;
|
||||
}
|
||||
|
||||
const RULE_COLUMNS = {
|
||||
id: true,
|
||||
active: true,
|
||||
config: true,
|
||||
actionTemplate: true,
|
||||
} as const;
|
||||
|
||||
function toActiveRule<K extends AutomationTriggerType>(
|
||||
trigger: K,
|
||||
row: RuleRow | null,
|
||||
): ActiveRule<K> {
|
||||
if (row === null) {
|
||||
const defaults = DEFAULT_RULES[trigger];
|
||||
return {
|
||||
id: null,
|
||||
active: defaults.active ?? true,
|
||||
config: ruleConfigOf(trigger, defaults.config),
|
||||
actionTemplate: defaults.actionTemplate,
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: row.id,
|
||||
active: row.active,
|
||||
config: ruleConfigOf(trigger, row.config),
|
||||
actionTemplate: row.actionTemplate,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The rule as it is in force: its row, or the built-in setting while there is
|
||||
* none. The config is parsed, so every key is valid.
|
||||
*/
|
||||
export async function readRule<K extends AutomationTriggerType>(
|
||||
db: DbClient,
|
||||
trigger: K,
|
||||
): Promise<ActiveRule<K>> {
|
||||
const row = await db.automationRule.findUnique({
|
||||
where: { triggerType: trigger },
|
||||
select: RULE_COLUMNS,
|
||||
});
|
||||
return toActiveRule(trigger, row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the rule row if it is missing (without touching an existing one),
|
||||
* locks it until the transaction ends and reads it. Serialises everything
|
||||
* that decides "is there already an open task/suggestion" under the rule.
|
||||
*/
|
||||
export async function lockRuleIn<K extends AutomationTriggerType>(
|
||||
tx: Prisma.TransactionClient,
|
||||
trigger: K,
|
||||
): Promise<ActiveRule<K> & { id: string }> {
|
||||
assertTransactionClient(tx, 'lockRuleIn');
|
||||
const defaults = DEFAULT_RULES[trigger];
|
||||
await tx.automationRule.createMany({
|
||||
data: [
|
||||
{
|
||||
triggerType: trigger,
|
||||
active: defaults.active ?? true,
|
||||
config: defaults.config as Prisma.InputJsonValue,
|
||||
actionTemplate: defaults.actionTemplate,
|
||||
},
|
||||
],
|
||||
skipDuplicates: true,
|
||||
});
|
||||
const { id } = await tx.automationRule.findUniqueOrThrow({
|
||||
where: { triggerType: trigger },
|
||||
select: { id: true },
|
||||
});
|
||||
await lockRowForUpdate(tx, 'AutomationRule', id);
|
||||
// Read after the lock: a concurrent edit of the rule is visible now.
|
||||
const row = await tx.automationRule.findUniqueOrThrow({
|
||||
where: { id },
|
||||
select: RULE_COLUMNS,
|
||||
});
|
||||
return { ...toActiveRule(trigger, row), id };
|
||||
}
|
||||
Reference in new issue
Block a user