Skip to content

feat: support group member fields as group-scoped managed resources #135

Description

@bwl21

Context

ct adopt group can turn an existing group hierarchy into portable TypeScript config, which is useful for recurring structures such as:

OJBP 2025/26
├── OJBP 1. Praktikum 25/26
├── OJBP 2. Praktikum 25/26
└── OJBP 3. Praktikum 25/26

The missing structural piece is group member fields. A dynamic ruleset may write fields such as praktikum-1, praktikum-2, and praktikum-3; adopting the group and ruleset without those definitions produces an incomplete blueprint.

This is distinct from group custom fields in #48 / #60 (/dbfields, e.g. fieldCategory.table == "cdb_gruppe").

Resource semantics

A group member field has a ChurchTools id, but belongs to exactly one group and is not globally reusable. Its portable identity must therefore be scoped by the managed group:

ojbp_2026_27_praktikum_1::wahl

Two groups declaring a field with the same local key/name must remain independent resources with different ChurchTools ids. Host-specific field ids must never appear in authored config or adopted blueprints.

Possible DSL (exact shape is open):

ct.group({
  key: "ojbp_2026_27_praktikum_1",
  name: "OJBP 1. Praktikum 26/27",
  memberFields: [
    {
      key: "wahl",
      name: "Wahl",
      fieldTypeCode: "text",
      requiredInRegistrationForm: true,
    },
  ],
});

An equivalent ct.groupMemberField({ group, key, ... }) resource is fine if it preserves the same ownership and dependency semantics.

Verified ChurchTools API surface

Current generated OpenAPI clients expose:

  • GET /groups/{groupId}/memberfields — read configured member-field definitions
  • POST /groups/{groupId}/memberfields/group — create a custom group member field
  • PATCH|PUT /groups/{groupId}/memberfields/group/{groupMemberFieldId} — update it
  • DELETE /groups/{groupId}/memberfields/group/{groupMemberFieldId} — delete it

Readable/writable properties include name, referenceName, fieldTypeCode, defaultValue, options, nameInSignupForm, note, noteInSignupForm, requiredInRegistrationForm, useInRegistrationForm, securityLevel, and sortKey (subject to the instance OpenAPI re-audit/live probe guardrails already used by this project).

Scope

  • Read group member fields for an existing group.
  • Add opt-in adoption, e.g.:
    • ct adopt group <id> --with-member-fields
    • ct adopt group --children-of <id> --with-member-fields
  • Emit all API-round-trippable properties without ChurchTools field ids.
  • Resolve identity as managed group key + local field key.
  • Make plan detect creates and updates to field definitions.
  • Make apply create fields only after their owning group exists, and update existing fields idempotently.
  • Never delete a field merely because it disappeared from config; require an explicit destructive operation/flag and preserve existing guardrails.
  • Validate dynamic rulesets during plan where possible: a reference to a group-scoped member field that does not exist or is not declared for the target group should fail/warn before apply.
  • Order apply so newly created fields exist before a dependent dynamic ruleset is installed.

Blueprint use case

function ojbp(ct: ConfigContext, year: string) {
  // groups + children + group-scoped member fields + dynamic ruleset
}

ojbp(ct, "25/26");
ojbp(ct, "26/27");

The second call creates new groups and new field ids for 26/27; no 25/26 field id participates in resolution.

Acceptance criteria

  1. Adopt an existing group with two group member fields.
  2. The emitted config contains both definitions and no host-specific field ids.
  3. Declare the same structure under a new group key.
  4. ct plan shows one new group and two new group member fields.
  5. ct apply creates the group and both fields; a second plan is a no-op.
  6. A dynamic ruleset can resolve/reference those newly created fields.
  7. Two groups with the same local field key receive different ChurchTools ids without a state-key collision.
  8. An end-to-end test covers the complete adopt → re-key/blueprint → plan → apply flow.

Relates to #14/#18 (dynamic rulesets), #21 (managed group fields), #48/#60 (different custom-field domain), #51/#52 (adopt/blueprint ergonomics).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageUnsorted intake — decide in the weekly sweep

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions