← Frontend reviews · All model classes

Coflyr · Frontend form review · 10 October 2026

ConfigurationMaster

Configuration definition, defaults, relationships and shared effectivity

Backend model: ReviewedGUI: Pending review3 workspaces · 27 tabs142 control appearances

Capture coverage: The three reviews contain 80 live application screenshots, 4 navigation-destination references, 18 references to the same shared form captured in another owning workspace, and 116 static JavaFX source layouts (112 declared tabs plus 4 runtime Revision tabs). 14 disabled or unreachable tabs have source layouts only. Each section states which evidence it has. Live images show blank forms and the complete application shell at 960 × 640; use the full static layouts to see controls below the visible window. Selected-record workflows, validation and persistence remain pending.

Use the decision and notes controls to specify GUI changes. Drafts stay in this browser; Export decisions saves a portable JSON file. Import that file to resume in another browser. These controls do not update backend models, the database or the desktop application. Source files and the reviewed backend catalog are linked as evidence; a matching view-model property is a review aid and does not prove API persistence.

Open reviewed ConfigurationMaster backend model · Backend-to-GUI coverage · Runtime review checklist

Drafts save locally in this browser

Observed GUI points for review

These are review observations, not approved changes. No application models or tables have been modified.

GUI decisions to make

Configuration Master

Configuration Master / Profile

GUI review pending

Configuration master profile.

Direct application capture — Captured from the running local Coflyr application. Blank form; no selected saved record. Search grids and shared actions are included.

Direct application capture: Configuration Master / Profile
Direct application capture · 2026-10-10T12:28:25.342273 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Configuration Master / Profile
Static JavaFX layout · 1280 × 284 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (6)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Configuration Code *
configurationMasterCodeField
TextField
configurationMasterCodeProperty
promptText=Enter configuration code
Configuration Name *
configurationNameField
TextField
configurationNameProperty
promptText=Enter configuration name
Configuration Type
configurationTypeComboBox
ComboBox
configurationTypeProperty
Review requiredness, validation and display.
Source
sourceComboBox
ComboBox
sourceProperty
Review requiredness, validation and display.
FP Generator Mode
defaultFpGeneratorModeComboBox
ComboBox
defaultFpGeneratorModeProperty
Review requiredness, validation and display.
Alternate Mode
alternateModeComboBox
ComboBox
alternateModeProperty
Review requiredness, validation and display.
Source evidence

Tab key: configurationmaster--profile
Section: Entire form
FXML SHA-256: 7c1c4425edca208b85c1f67f9c7c81e30c0b805d6974a33ad391276ef38846fd

coflyr-fe/src/main/resources/com/coflyr/fe/configuration/views/configuration-master-form.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.configuration.controllers.ConfigurationMasterFormController"
      fillWidth="true"
      maxWidth="Infinity"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="1 Configuration Master"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label fx:id="configurationCodeLabel" styleClass="field-label" text="Configuration Code *"/>
                        <TextField fx:id="configurationMasterCodeField" maxWidth="Infinity" prefWidth="1.0" promptText="Enter configuration code" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label fx:id="configurationNameLabel" styleClass="field-label" text="Configuration Name *"/>
                        <TextField fx:id="configurationNameField" maxWidth="Infinity" prefWidth="1.0" promptText="Enter configuration name" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <Label fx:id="configurationTypeLabel" styleClass="field-label" text="Configuration Type"/>
                        <ComboBox fx:id="configurationTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="1" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <Label fx:id="sourceLabel" styleClass="field-label" text="Source"/>
                        <ComboBox fx:id="sourceComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2">
                    <children>
                        <Label fx:id="fpGeneratorModeLabel" styleClass="field-label" text="FP Generator Mode"/>
                        <ComboBox fx:id="defaultFpGeneratorModeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="1" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2">
                    <children>
                        <Label fx:id="alternateModeLabel" styleClass="field-label" text="Alternate Mode"/>
                        <ComboBox fx:id="alternateModeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.configuration.controllers.ConfigurationMasterFormController
package com.coflyr.fe.configuration.controllers;

import com.coflyr.fe.configuration.viewmodels.ConfigurationMasterViewModel;
import com.coflyr.fe.configuration.services.ConfigurationMasterHelperTextService;
import com.coflyr.fe.shared.gui.EnumDisplayLabels;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.shared.gui.ComboBoxOptionHelp;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ConfigurationMasterFormController {
    private final ConfigurationMasterViewModel viewModel;

    @FXML private TextField configurationMasterCodeField;
    @FXML private TextField configurationNameField;
    @FXML private ComboBox<String> configurationTypeComboBox;
    @FXML private ComboBox<String> sourceComboBox;
    @FXML private ComboBox<String> defaultFpGeneratorModeComboBox;
    @FXML private ComboBox<String> alternateModeComboBox;
    @FXML private Label configurationCodeLabel;
    @FXML private Label configurationNameLabel;
    @FXML private Label configurationTypeLabel;
    @FXML private Label sourceLabel;
    @FXML private Label fpGeneratorModeLabel;
    @FXML private Label alternateModeLabel;

    public ConfigurationMasterFormController(ConfigurationMasterViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        setFieldHelp(configurationCodeLabel, "ConfigurationCode");
        setFieldHelp(configurationNameLabel, "ConfigurationName");
        setFieldHelp(configurationTypeLabel, "ConfigurationType");
        setFieldHelp(sourceLabel, "Source");
        setFieldHelp(fpGeneratorModeLabel, "FpGeneratorMode");
        setFieldHelp(alternateModeLabel, "AlternateMode");
        configurationTypeComboBox.setItems(viewModel.configurationTypes());
        configurationTypeComboBox.setConverter(displayConverter());
        ComboBoxOptionHelp.install(configurationTypeComboBox, ConfigurationMasterHelperTextService.options("ConfigurationType"));
        sourceComboBox.setItems(viewModel.sources());
        sourceComboBox.setConverter(displayConverter());
        ComboBoxOptionHelp.install(sourceComboBox, ConfigurationMasterHelperTextService.options("Source"));
        configurationMasterCodeField.textProperty().bindBidirectional(viewModel.configurationMasterCodeProperty());
        configurationNameField.textProperty().bindBidirectional(viewModel.configurationNameProperty());
        configurationTypeComboBox.valueProperty().bindBidirectional(viewModel.configurationTypeProperty());
        sourceComboBox.valueProperty().bindBidirectional(viewModel.sourceProperty());
        sourceComboBox.setDisable(true);
        sourceComboBox.getSelectionModel().select("NewConfiguration");
        if (defaultFpGeneratorModeComboBox != null) {
            defaultFpGeneratorModeComboBox.setItems(viewModel.fpGeneratorModes());
            defaultFpGeneratorModeComboBox.setConverter(displayConverter());
            defaultFpGeneratorModeComboBox.valueProperty().bindBidirectional(viewModel.defaultFpGeneratorModeProperty());
            ComboBoxOptionHelp.install(defaultFpGeneratorModeComboBox, ConfigurationMasterHelperTextService.options("FpGeneratorMode"));
        }
        if (alternateModeComboBox != null) {
            alternateModeComboBox.getItems().setAll("ImportCurrentAlternates", "DynamicallyAllowAlternates");
            alternateModeComboBox.setConverter(displayConverter());
            alternateModeComboBox.valueProperty().bindBidirectional(viewModel.alternateModeProperty());
            ComboBoxOptionHelp.install(alternateModeComboBox, ConfigurationMasterHelperTextService.options("AlternateMode"));
        }
    }

    private void setFieldHelp(Label label, String key) {
        String text = ConfigurationMasterHelperTextService.field(key);
        if (label != null && !text.isBlank()) {
            com.coflyr.fe.shared.gui.WindowBoundHelp.install(label, text);
        }
    }

    private StringConverter<String> displayConverter() {
        return new StringConverter<>() {
            @Override
            public String toString(String key) {
                return EnumDisplayLabels.display(key);
            }

            @Override
            public String fromString(String label) {
                return EnumDisplayLabels.key(label);
            }
        };
    }
}

Configuration Master / Defaults

GUI review pending

Configuration master defaults.

Direct application capture — Captured from the running local Coflyr application. Blank form; no selected saved record. Search grids and shared actions are included.

Direct application capture: Configuration Master / Defaults
Direct application capture · 2026-10-10T12:28:28.785205 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Configuration Master / Defaults
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (6)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Checker Scope
checkerScopeComboBox
ComboBox
checkerScopeProperty
Review requiredness, validation and display.
Sub-configuration Mode
defaultSubConfigurationModeComboBox
ComboBox
defaultSubConfigurationModeProperty
Review requiredness, validation and display.
Imported Specification
importedSpecificationField
TextField
importedSpecificationProperty
Review requiredness, validation and display.
Effectivity Main Part Only
defaultEffectivityMainPartOnlyCheckBox
CheckBox
defaultEffectivityMainPartOnlyProperty
Review requiredness, validation and display.
Include Alternates
includeAlternatesCheckBox
CheckBox
includeAlternatesProperty
Review requiredness, validation and display.
Import Consumables
importConsumablesCheckBox
CheckBox
importConsumablesProperty
Review requiredness, validation and display.
Source evidence

Tab key: configurationmaster--defaults
Section: Entire form
FXML SHA-256: 420ff2241e8df4c0d97c81ce88ca8a87539c5538b125dc1bce35e66d7417a639

coflyr-fe/src/main/resources/com/coflyr/fe/configuration/views/configuration-master-defaults-form.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.configuration.controllers.ConfigurationMasterDefaultsController"
      fillWidth="true"
      maxWidth="Infinity"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Default Generation"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <!-- Description Source is reserved until controlled import sources are implemented. -->
                <VBox styleClass="field-group" GridPane.rowIndex="1" GridPane.columnIndex="0" maxWidth="Infinity"><children><Label fx:id="checkerScopeLabel" text="Checker Scope" styleClass="field-label"/><ComboBox fx:id="checkerScopeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.rowIndex="1" GridPane.columnIndex="1" maxWidth="Infinity"><children><Label fx:id="subConfigurationModeLabel" text="Sub-configuration Mode" styleClass="field-label"/><ComboBox fx:id="defaultSubConfigurationModeComboBox" maxWidth="Infinity" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.rowIndex="1" GridPane.columnIndex="2" maxWidth="Infinity"><children><Label fx:id="importedSpecificationLabel" text="Imported Specification" styleClass="field-label"/><TextField fx:id="importedSpecificationField" maxWidth="Infinity" styleClass="text-input"/></children></VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>

        <Label styleClass="section-title" text="Default Flags"/>
        <FlowPane hgap="10" styleClass="form-checkbox-pane" vgap="8">
            <children>
                <CheckBox fx:id="defaultEffectivityMainPartOnlyCheckBox" focusTraversable="false" styleClass="form-check-box" text="Effectivity Main Part Only"/>
                <CheckBox fx:id="includeAlternatesCheckBox" styleClass="form-check-box" text="Include Alternates"/>
                <CheckBox fx:id="importConsumablesCheckBox" styleClass="form-check-box" text="Import Consumables"/>
            </children>
        </FlowPane>
    </children>
</VBox>
Controller: com.coflyr.fe.configuration.controllers.ConfigurationMasterDefaultsController
package com.coflyr.fe.configuration.controllers;

import com.coflyr.fe.configuration.viewmodels.ConfigurationMasterViewModel;
import com.coflyr.fe.configuration.services.ConfigurationMasterHelperTextService;
import com.coflyr.fe.shared.gui.EnumDisplayLabels;
import com.coflyr.fe.shared.gui.ComboBoxOptionHelp;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ConfigurationMasterDefaultsController {
    private final ConfigurationMasterViewModel viewModel;

    @FXML private CheckBox defaultEffectivityMainPartOnlyCheckBox;
    @FXML private Label checkerScopeLabel;
    @FXML private Label subConfigurationModeLabel;
    @FXML private Label importedSpecificationLabel;
    @FXML private ComboBox<String> checkerScopeComboBox;
    @FXML private ComboBox<String> defaultSubConfigurationModeComboBox;
    @FXML private javafx.scene.control.TextField importedSpecificationField;
    @FXML private CheckBox includeAlternatesCheckBox;
    @FXML private CheckBox importConsumablesCheckBox;

    public ConfigurationMasterDefaultsController(ConfigurationMasterViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        // Description Source is disabled until controlled import sources exist.
        setFieldHelp(checkerScopeLabel, "CheckerScope");
        setFieldHelp(subConfigurationModeLabel, "SubConfigurationMode");
        setFieldHelp(importedSpecificationLabel, "ImportedSpecification");
        setFieldHelp(defaultEffectivityMainPartOnlyCheckBox, "EffectivityMainPartOnly");
        setFieldHelp(includeAlternatesCheckBox, "IncludeAlternates");
        setFieldHelp(importConsumablesCheckBox, "ImportConsumables");
        checkerScopeComboBox.getItems().setAll("All rotables", "Only rotables with requirements or documents");
        checkerScopeComboBox.setConverter(displayConverter());
        ComboBoxOptionHelp.install(checkerScopeComboBox, ConfigurationMasterHelperTextService.options("CheckerScope"));
        checkerScopeComboBox.valueProperty().bindBidirectional(viewModel.checkerScopeProperty());
        defaultSubConfigurationModeComboBox.setItems(viewModel.subConfigurationModes());
        defaultSubConfigurationModeComboBox.setConverter(displayConverter());
        ComboBoxOptionHelp.install(defaultSubConfigurationModeComboBox, ConfigurationMasterHelperTextService.options("SubConfigurationMode"));
        defaultSubConfigurationModeComboBox.valueProperty().bindBidirectional(viewModel.defaultSubConfigurationModeProperty());
        importedSpecificationField.textProperty().bindBidirectional(viewModel.importedSpecificationProperty());
        includeAlternatesCheckBox.selectedProperty().bindBidirectional(viewModel.includeAlternatesProperty());
        importConsumablesCheckBox.selectedProperty().bindBidirectional(viewModel.importConsumablesProperty());
        defaultEffectivityMainPartOnlyCheckBox.selectedProperty().bindBidirectional(viewModel.defaultEffectivityMainPartOnlyProperty());
    }

    private void setFieldHelp(javafx.scene.control.Control control, String key) {
        String text = ConfigurationMasterHelperTextService.field(key);
        if (control != null && !text.isBlank()) {
            com.coflyr.fe.shared.gui.WindowBoundHelp.install(control, text);
        }
    }

    private StringConverter<String> displayConverter() {
        return new StringConverter<>() {
            @Override
            public String toString(String key) {
                return EnumDisplayLabels.display(key);
            }

            @Override
            public String fromString(String label) {
                return EnumDisplayLabels.key(label);
            }
        };
    }
}

Configuration Master / Effectivities

GUI review pending

Assign existing Effectivity records.

Direct application capture — Captured from the running local Coflyr application. Blank form; no selected saved record. Search grids and shared actions are included.

Direct application capture: Configuration Master / Effectivities
Direct application capture · 2026-10-10T12:28:32.191280 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Configuration Master / Effectivities
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (1)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Effectivity
effectivityLookup
UUID search lookup
effectivityIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Source evidence

Tab key: configurationmaster--effectivities
Section: Entire form
FXML SHA-256: 0c16d941893d4bbe42b9715cb7763d2ec1c3496d897c8888a2ac1fb26681143c

coflyr-fe/src/main/resources/com/coflyr/fe/configuration/views/configuration-master-effectivities-form.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.configuration.controllers.ConfigurationMasterEffectivitiesController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      spacing="8"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Assigned Effectivities"/>
        <Label styleClass="field-hint"
               text="Assign existing Effectivity records here. Maintain their rules and validity in the separate Effectivity form."
               wrapText="true"/>
        <fx:include fx:id="effectivityLookup" source="../../views/controls/db-search-lookup-field.fxml"/>
    </children>
</VBox>
Controller: com.coflyr.fe.configuration.controllers.ConfigurationMasterEffectivitiesController
package com.coflyr.fe.configuration.controllers;

import com.coflyr.fe.configuration.viewmodels.ConfigurationMasterViewModel;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.fxml.FXML;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public final class ConfigurationMasterEffectivitiesController {
    private final ConfigurationMasterViewModel viewModel;

    @FXML private DbSearchLookupFieldController effectivityLookupController;

    public ConfigurationMasterEffectivitiesController(ConfigurationMasterViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        LookupFieldBinding.bind(
                effectivityLookupController,
                "effectivity",
                "Effectivity",
                "Search existing Effectivity",
                viewModel.effectivityIdProperty(),
                viewModel.effectivityLookupQueryProperty());
        viewModel.selectedConfigurationMasterIdProperty().addListener((observable, oldValue, newValue) ->
                viewModel.refreshEffectivityLinks());
        viewModel.refreshEffectivityLinks();
    }
}

Configuration Master / Remarks

GUI review pending

Configuration master remarks.

Direct application capture — Captured from the running local Coflyr application. Blank form; no selected saved record. Search grids and shared actions are included.

Direct application capture: Configuration Master / Remarks
Direct application capture · 2026-10-10T12:28:39.095793 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Configuration Master / Remarks
Static JavaFX layout · 1280 × 284 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (3)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Description
descriptionField
TextField
descriptionProperty
promptText=Description
Details
detailsField
TextField
detailsProperty
promptText=Details
General Remarks
remarksField
TextField
remarksProperty
promptText=General remarks
Source evidence

Tab key: configurationmaster--remarks
Section: Entire form
FXML SHA-256: b9938281a3c8cda2d4d5f402e9b346f99202fe71e9fe5fe2ae56d2077ce08254

coflyr-fe/src/main/resources/com/coflyr/fe/shared/remarks/views/remarks-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.remarks.controllers.RemarksSectionController"
      fillWidth="true"
      maxWidth="Infinity"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Universal Remarks"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Description"/>
                        <TextField fx:id="descriptionField" maxWidth="Infinity" prefWidth="1.0" promptText="Description" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <Label styleClass="field-label" text="Details"/>
                        <TextField fx:id="detailsField" maxWidth="Infinity" prefWidth="1.0" promptText="Details" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2">
                    <children>
                        <Label styleClass="field-label" text="General Remarks"/>
                        <TextField fx:id="remarksField" maxWidth="Infinity" prefWidth="1.0" promptText="General remarks" styleClass="text-input"/>
                    </children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="100.0"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.remarks.controllers.RemarksSectionController
package com.coflyr.fe.shared.remarks.controllers;

import com.coflyr.fe.config.DisposableView;
import com.coflyr.fe.shared.remarks.viewmodels.RemarksViewModel;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RemarksSectionController implements DisposableView {
    private RemarksViewModel viewModel;

    @FXML private TextField descriptionField;
    @FXML private TextField detailsField;
    @FXML private TextField remarksField;

    @FXML
    private void initialize() {
        bindTo(viewModel);
    }

    public void bindTo(RemarksViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        unbindCurrentViewModel();
        this.viewModel = viewModel;
        descriptionField.textProperty().bindBidirectional(viewModel.descriptionProperty());
        detailsField.textProperty().bindBidirectional(viewModel.detailsProperty());
        remarksField.textProperty().bindBidirectional(viewModel.remarksProperty());
    }

    private void unbindCurrentViewModel() {
        if (viewModel == null || descriptionField == null || detailsField == null || remarksField == null) {
            return;
        }
        descriptionField.textProperty().unbindBidirectional(viewModel.descriptionProperty());
        detailsField.textProperty().unbindBidirectional(viewModel.detailsProperty());
        remarksField.textProperty().unbindBidirectional(viewModel.remarksProperty());
    }

    @Override
    public void disposeView() {
        unbindCurrentViewModel();
        viewModel = null;
    }
}

Configuration Master / Audit

GUI review pending

Configuration master audit details.

Direct application capture — Captured from the running local Coflyr application. Blank form; no selected saved record. Search grids and shared actions are included.

Direct application capture: Configuration Master / Audit
Direct application capture · 2026-10-10T12:28:43.077142 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Configuration Master / Audit
Static JavaFX layout · 1280 × 490 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (12)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Business Status
businessStatusComboBox
ComboBox
businessStatusProperty
Review requiredness, validation and display.
Approval Status
approvalStatusComboBox
ComboBox
approvalStatusProperty
Review requiredness, validation and display.
valid From
validFromField
Date/time input
Controller-defined
Review timezone, format, requiredness and read-only states.
valid To
validToField
Date/time input
Controller-defined
Review timezone, format, requiredness and read-only states.
audit Date
auditDateField
Date/time input
Controller-defined
Review timezone, format, requiredness and read-only states.
effective Date
effectiveDateField
Date/time input
Controller-defined
Review timezone, format, requiredness and read-only states.
audit Activation Date
auditActivationDateField
Date/time input
Controller-defined
Review timezone, format, requiredness and read-only states.
Audit Remarks
auditRemarksField
TextField
auditRemarksProperty
promptText=Audit remarks
Pre Approved
preApprovedCheckBox
CheckBox
preApprovedProperty
Review requiredness, validation and display.
Sent To User
sentToUserLookup
UUID search lookup
sentToUserIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Sent To Department
sentToDepartmentLookup
UUID search lookup
sentToDepartmentIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Status Information
statusInformationField
TextArea
statusInformationProperty
editable=false; promptText=Maintained by the workflow
Source evidence

Tab key: configurationmaster--audit
Section: Entire form
FXML SHA-256: 90712c80993bb2afd680a31fb3fac9ef1511dd5eecb67b10d3d245f5077ddfbb

coflyr-fe/src/main/resources/com/coflyr/fe/shared/audit/views/audit-min-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.audit.controllers.AuditMinSectionController"
      fillWidth="true"
      maxWidth="Infinity"
      spacing="7"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="System Audit"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Business Status"/>
                        <ComboBox fx:id="businessStatusComboBox" maxWidth="Infinity" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="1" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Approval Status"/>
                        <ComboBox fx:id="approvalStatusComboBox" maxWidth="Infinity" styleClass="text-input"/>
                    </children>
                </VBox>
                <fx:include fx:id="validFromField" source="../../../views/controls/datetime-field.fxml" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
                <fx:include fx:id="validToField" source="../../../views/controls/datetime-field.fxml" GridPane.columnIndex="1" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
                <fx:include fx:id="auditDateField" source="../../../views/controls/datetime-field.fxml" GridPane.columnIndex="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0"/>
                <fx:include fx:id="effectiveDateField" source="../../../views/controls/datetime-field.fxml" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2"/>
                <fx:include fx:id="auditActivationDateField" source="../../../views/controls/datetime-field.fxml" GridPane.columnIndex="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3">
                    <children>
                        <Label styleClass="field-label" text="Audit Remarks"/>
                        <TextField fx:id="auditRemarksField" maxWidth="Infinity" prefWidth="1.0" promptText="Audit remarks" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3">
                    <children>
                        <Label styleClass="field-label" text="Pre Approval"/>
                        <CheckBox fx:id="preApprovedCheckBox" focusTraversable="false" styleClass="form-check-box" text="Pre Approved"/>
                    </children>
                </VBox>
                <fx:include fx:id="sentToUserLookup" source="../../../views/controls/db-search-lookup-field.fxml" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="4"/>
                <fx:include fx:id="sentToDepartmentLookup" source="../../../views/controls/db-search-lookup-field.fxml" GridPane.columnIndex="1" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="4"/>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="5"><Label styleClass="field-label" text="Status Information"/><TextArea fx:id="statusInformationField" editable="false" maxWidth="Infinity" prefRowCount="2" wrapText="true" styleClass="form-text-area" promptText="Maintained by the workflow"/></VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.audit.controllers.AuditMinSectionController
package com.coflyr.fe.shared.audit.controllers;

import com.coflyr.fe.config.DisposableView;
import com.coflyr.fe.services.DateTimeParserService;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.shared.audit.viewmodels.AuditViewModel;
import com.coflyr.fe.shared.audit.viewmodels.EffectiveDateAuditViewModel;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import com.coflyr.fe.views.DateTimeFieldController;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeParseException;

/**
 * The Pre Approved checkbox is optional here (fx:id may be absent, e.g. the
 * Training Gap audit view - Pre Approved lives on its main Gap tab instead).
 * The apply-defaults-when-checked behavior is sourced from the ViewModel's
 * own preApprovedProperty, not the local checkbox, so it still works
 * correctly when Pre Approved is toggled from a different tab entirely.
 * Fields here are never disabled based on Pre Approved - viewing/editing the
 * audit state should not be locked out just because the record happens to
 * be pre-approved.
 */
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AuditMinSectionController implements DisposableView {
    private AuditViewModel viewModel;
    private final DateTimeParserService dateTimeParserService;
    private ChangeListener<String> auditDateListener;
    private ChangeListener<String> validFromListener;
    private ChangeListener<String> validToListener;
    private ChangeListener<String> effectiveDateListener;
    private ChangeListener<LocalDateTime> auditActivationDateListener;
    private ChangeListener<Boolean> preApprovedListener;

    @FXML private ComboBox<String> businessStatusComboBox;
    @FXML private ComboBox<String> approvalStatusComboBox;
    @FXML private Node auditDateField;
    @FXML private DateTimeFieldController auditDateFieldController;
    @FXML private DateTimeFieldController validFromFieldController;
    @FXML private DateTimeFieldController validToFieldController;
    @FXML private Node effectiveDateField;
    @FXML private DateTimeFieldController effectiveDateFieldController;
    @FXML private CheckBox preApprovedCheckBox;
    @FXML private TextField auditRemarksField;
    @FXML private javafx.scene.control.TextArea statusInformationField;
    @FXML private DateTimeFieldController auditActivationDateFieldController;
    @FXML private DbSearchLookupFieldController sentToUserLookupController;
    @FXML private DbSearchLookupFieldController sentToDepartmentLookupController;

    public AuditMinSectionController(DateTimeParserService dateTimeParserService) {
        this.dateTimeParserService = dateTimeParserService;
    }

    @FXML
    private void initialize() {
        auditDateFieldController.configure("Audit Date", "Audit date and time", "HH:mm");
        auditDateFieldController.selectedDateTimeProperty().addListener((observable, oldValue, newValue) -> updateAuditDateModel(newValue));
        validFromFieldController.configure("Valid From", "Start of validity", "HH:mm");
        validFromFieldController.selectedDateTimeProperty().addListener((observable, oldValue, newValue) -> updateDateTimeModel(viewModel == null ? null : viewModel.validFromProperty(), newValue));
        validToFieldController.configure("Valid To", "End of validity", "HH:mm");
        validToFieldController.selectedDateTimeProperty().addListener((observable, oldValue, newValue) -> updateDateTimeModel(viewModel == null ? null : viewModel.validToProperty(), newValue));
        effectiveDateFieldController.configure("Effective Date", "Date the record becomes effective", "HH:mm");
        auditActivationDateFieldController.configure("Activation Date", "File activation date", "HH:mm");
        effectiveDateFieldController.selectedDateTimeProperty().addListener((observable, oldValue, newValue) -> {
            if (viewModel instanceof EffectiveDateAuditViewModel effectiveDateModel) {
                updateDateTimeModel(effectiveDateModel.effectiveDateProperty(), newValue);
            }
        });
    }

    public void bindTo(AuditViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        unbindCurrentViewModel();
        this.viewModel = viewModel;
        if (statusInformationField != null) {
            statusInformationField.getParent().setVisible(viewModel.extendedRevisionControlsAvailableProperty().get());
            statusInformationField.getParent().setManaged(viewModel.extendedRevisionControlsAvailableProperty().get());
        }
        businessStatusComboBox.setItems(viewModel.businessStatuses());
        approvalStatusComboBox.setItems(viewModel.approvalStatuses());
        businessStatusComboBox.valueProperty().bindBidirectional(viewModel.businessStatusProperty());
        approvalStatusComboBox.valueProperty().bindBidirectional(viewModel.approvalStatusProperty());
        updateAuditDateControl(viewModel.auditDateProperty().get());
        auditDateListener = (observable, oldValue, newValue) -> updateAuditDateControl(newValue);
        viewModel.auditDateProperty().addListener(auditDateListener);
        updateDateTimeControl(validFromFieldController, viewModel.validFromProperty().get());
        validFromListener = (observable, oldValue, newValue) -> updateDateTimeControl(validFromFieldController, newValue);
        viewModel.validFromProperty().addListener(validFromListener);
        updateDateTimeControl(validToFieldController, viewModel.validToProperty().get());
        validToListener = (observable, oldValue, newValue) -> updateDateTimeControl(validToFieldController, newValue);
        viewModel.validToProperty().addListener(validToListener);
        boolean supportsEffectiveDate = viewModel instanceof EffectiveDateAuditViewModel;
        effectiveDateField.setVisible(supportsEffectiveDate);
        effectiveDateField.setManaged(supportsEffectiveDate);
        if (supportsEffectiveDate) {
            EffectiveDateAuditViewModel effectiveDateModel = (EffectiveDateAuditViewModel) viewModel;
            updateDateTimeControl(effectiveDateFieldController, effectiveDateModel.effectiveDateProperty().get());
            effectiveDateListener = (observable, oldValue, newValue) ->
                    updateDateTimeControl(effectiveDateFieldController, newValue);
            effectiveDateModel.effectiveDateProperty().addListener(effectiveDateListener);
        }
        if (preApprovedCheckBox != null) {
            preApprovedCheckBox.selectedProperty().bindBidirectional(viewModel.preApprovedProperty());
        }
        preApprovedListener = (observable, oldValue, newValue) -> {
            if (newValue) {
                applyPreApprovedSelections();
            }
            freezePreApprovedSelections(newValue);
        };
        viewModel.preApprovedProperty().addListener(preApprovedListener);
        // Apply on bind as well as on change, so a record that loads already
        // pre-approved opens with its selections frozen rather than editable
        // until the box is next toggled.
        freezePreApprovedSelections(viewModel.preApprovedProperty().get());
        auditRemarksField.textProperty().bindBidirectional(viewModel.auditRemarksProperty());
        if (statusInformationField != null) statusInformationField.textProperty().bind(viewModel.statusInformationProperty());
        updateDateTimeControl(auditActivationDateFieldController, viewModel.auditActivationDateProperty().get());
        auditActivationDateListener = (observable, oldValue, newValue) -> updateDateTimeModel(viewModel == null ? null : viewModel.auditActivationDateProperty(), newValue);
        auditActivationDateFieldController.selectedDateTimeProperty().addListener(auditActivationDateListener);
        LookupFieldBinding.bind(sentToUserLookupController, "applicationusers", "Sent To User",
                "Search user", viewModel.sentToUserIdProperty(), viewModel.sentToUserDisplayProperty());
        LookupFieldBinding.bind(sentToDepartmentLookupController, "department", "Sent To Department",
                "Search department", viewModel.sentToDepartmentIdProperty(), viewModel.sentToDepartmentDisplayProperty());
        if (viewModel.preApprovedProperty().get()) {
            applyPreApprovedSelections();
        }
    }

    private void unbindCurrentViewModel() {
        if (viewModel == null) {
            return;
        }
        businessStatusComboBox.valueProperty().unbindBidirectional(viewModel.businessStatusProperty());
        approvalStatusComboBox.valueProperty().unbindBidirectional(viewModel.approvalStatusProperty());
        if (auditDateListener != null) {
            viewModel.auditDateProperty().removeListener(auditDateListener);
            auditDateListener = null;
        }
        if (validFromListener != null) {
            viewModel.validFromProperty().removeListener(validFromListener);
            validFromListener = null;
        }
        if (validToListener != null) {
            viewModel.validToProperty().removeListener(validToListener);
            validToListener = null;
        }
        if (effectiveDateListener != null && viewModel instanceof EffectiveDateAuditViewModel effectiveDateModel) {
            effectiveDateModel.effectiveDateProperty().removeListener(effectiveDateListener);
            effectiveDateListener = null;
        }
        if (preApprovedCheckBox != null) {
            preApprovedCheckBox.selectedProperty().unbindBidirectional(viewModel.preApprovedProperty());
        }
        if (preApprovedListener != null) {
            viewModel.preApprovedProperty().removeListener(preApprovedListener);
            preApprovedListener = null;
        }
        auditRemarksField.textProperty().unbindBidirectional(viewModel.auditRemarksProperty());
        if (statusInformationField != null) statusInformationField.textProperty().unbind();
        // The form is reused across records, so hand the fields back enabled -
        // otherwise a pre-approved record would leave the next one frozen.
        freezePreApprovedSelections(false);
    }

    private void applyPreApprovedSelections() {
        // Rendering or reopening Audit must not activate an explicitly inactive record.
        // The view model handles defaults when the user actually ticks Pre Approved.
        viewModel.approvalStatusProperty().set("Released");
        approvalStatusComboBox.getSelectionModel().select("Released");
    }

    /**
     * Locks the selections pre-approval decides, so the values that were filled
     * in cannot then be cleared or changed to something that contradicts the
     * ticked box. Unticking hands them back.
     *
     * <p>Disabled rather than made read-only because a JavaFX ComboBox has no
     * read-only mode: it is either editable or not, and a non-editable combo
     * still opens its list and accepts a different choice.</p>
     */
    private void freezePreApprovedSelections(boolean frozen) {
        if (businessStatusComboBox != null) {
            businessStatusComboBox.setDisable(frozen);
        }
        if (approvalStatusComboBox != null) {
            approvalStatusComboBox.setDisable(frozen);
        }
        if (auditDateField != null) {
            auditDateField.setDisable(frozen);
        }
    }

    private void updateAuditDateControl(String value) {
        try {
            auditDateFieldController.setSelectedDateTime(dateTimeParserService.parseDateTime(value));
        } catch (DateTimeParseException ignored) {
            auditDateFieldController.clearSelection();
        }
    }

    private void updateAuditDateModel(LocalDateTime value) {
        String formattedValue = dateTimeParserService.formatDateTime(value);
        if (!formattedValue.equals(viewModel.auditDateProperty().get())) {
            viewModel.auditDateProperty().set(formattedValue);
        }
    }

    private void updateDateTimeControl(DateTimeFieldController controller, String value) {
        try {
            controller.setSelectedDateTime(dateTimeParserService.parseDateTime(value));
        } catch (DateTimeParseException ignored) {
            controller.clearSelection();
        }
    }

    private void updateDateTimeModel(javafx.beans.property.StringProperty property, LocalDateTime value) {
        if (property == null) {
            return;
        }
        String formattedValue = dateTimeParserService.formatDateTime(value);
        if (!formattedValue.equals(property.get())) {
            property.set(formattedValue);
        }
    }

    @Override
    public void disposeView() {
        unbindCurrentViewModel();
        viewModel = null;
    }
}

Configuration Master / Revision

GUI review pending

Shared Revision tab observed in the running application; injected by the workspace shell.

Direct application capture — Captured from the running local Coflyr application. Blank form; no selected saved record. Search grids and shared actions are included.

Direct application capture: Configuration Master / Revision
Direct application capture · 2026-10-10T12:28:35.651052 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Configuration Master / Revision
Static JavaFX layout · 1280 × 756 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (13)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Issue Number
issueNumberField
TextField
issueNumberProperty
promptText=Issue number
Issue Date
issueDateField
DateTimeField
issueDateProperty
Review requiredness, validation and display.
Revision Number
revisionNumberField
TextField
revisionNumberProperty
promptText=Revision number
Revision Date
revisionDateField
DateTimeField
revisionDateProperty
Review requiredness, validation and display.
Revision Type
revisionTypeComboBox
ComboBox
revisionTypeProperty
Review requiredness, validation and display.
Temporary Revision
temporaryRevisionCheckBox
CheckBox
temporaryRevisionProperty
Review requiredness, validation and display.
Issue Revision Title
issueRevisionTitleField
TextField
issueRevisionTitleProperty
promptText=Issue revision title
Revision Reason
revisionReasonField
TextArea
revisionReasonProperty
promptText=Revision reason
Reason for Issue
reasonForIssueField
TextArea
reasonForIssueProperty
Review requiredness, validation and display.
Summary of Changes
summaryOfChangeRemarksField
TextArea
summaryOfChangeRemarksProperty
Review requiredness, validation and display.
Issue Notes
issueNotesField
TextArea
issueNotesProperty
Review requiredness, validation and display.
Revision Notes
revisionNotesField
TextArea
revisionNotesProperty
Review requiredness, validation and display.
Previous Revision Record
revisionLinkIdField
TextField
revisionLinkIdProperty
editable=false; promptText=Assigned when a revision is filed
Source evidence

Tab key: configurationmaster--revision
Section: Entire form
FXML SHA-256: d17835fc44e81b5fbff375b561866ba58e1a7932674fe44bece254cb21e81f1f

coflyr-fe/src/main/resources/com/coflyr/fe/shared/audit/views/audit-issue-revision-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import com.coflyr.fe.views.DateTimeField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.audit.controllers.AuditIssueRevisionSectionController"
      fillWidth="true"
      maxWidth="Infinity"
      spacing="7"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Issue / Revision"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Issue Number"/>
                        <TextField fx:id="issueNumberField" maxWidth="Infinity" promptText="Issue number" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="1" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Issue Date"/>
                        <DateTimeField dateOnly="true" fx:id="issueDateField" maxWidth="Infinity"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Revision Number"/>
                        <TextField fx:id="revisionNumberField" maxWidth="Infinity" promptText="Revision number" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <Label styleClass="field-label" text="Revision Date"/>
                        <DateTimeField dateOnly="true" fx:id="revisionDateField" maxWidth="Infinity"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="1" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <Label styleClass="field-label" text="Revision Type"/>
                        <ComboBox fx:id="revisionTypeComboBox" maxWidth="Infinity" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <Label styleClass="field-label" text="Temporary Revision"/>
                        <CheckBox fx:id="temporaryRevisionCheckBox" focusTraversable="false" styleClass="form-check-box" text="Temporary Revision"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2">
                    <children>
                        <Label styleClass="field-label" text="Issue Revision Title"/>
                        <TextField fx:id="issueRevisionTitleField" maxWidth="Infinity" prefWidth="1.0" promptText="Issue revision title" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3">
                    <children>
                        <Label styleClass="field-label" text="Revision Reason"/>
                        <TextArea fx:id="revisionReasonField" maxWidth="Infinity" prefRowCount="2" promptText="Revision reason" styleClass="form-text-area" wrapText="true"/>
                    </children>
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="4"><Label styleClass="field-label" text="Reason for Issue"/><TextArea fx:id="reasonForIssueField" maxWidth="Infinity" prefRowCount="2" wrapText="true" styleClass="form-text-area"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="5"><Label styleClass="field-label" text="Summary of Changes"/><TextArea fx:id="summaryOfChangeRemarksField" maxWidth="Infinity" prefRowCount="2" wrapText="true" styleClass="form-text-area"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="6"><Label styleClass="field-label" text="Issue Notes"/><TextArea fx:id="issueNotesField" maxWidth="Infinity" prefRowCount="2" wrapText="true" styleClass="form-text-area"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="7"><Label styleClass="field-label" text="Revision Notes"/><TextArea fx:id="revisionNotesField" maxWidth="Infinity" prefRowCount="2" wrapText="true" styleClass="form-text-area"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="8"><Label styleClass="field-label" text="Previous Revision Record"/><TextField fx:id="revisionLinkIdField" editable="false" maxWidth="Infinity" promptText="Assigned when a revision is filed" styleClass="text-input"/></VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.audit.controllers.AuditIssueRevisionSectionController
package com.coflyr.fe.shared.audit.controllers;

import com.coflyr.fe.config.DisposableView;
import com.coflyr.fe.shared.audit.viewmodels.AuditViewModel;
import com.coflyr.fe.views.DateTimeField;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
 * The record's Issue/Revision identity - separate from the System Audit
 * section (AuditSectionController) since these fields describe the
 * document's issue/revision state rather than its audit/approval state.
 * Bound to the SAME live AuditViewModel instance as the record's Audit tab.
 *
 * <p>These fields show the current record's identity. Filing a new revision is
 * the Revision command in the shared form action bar, not a control on this
 * section, because it acts on the record as a whole.</p>
 */
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AuditIssueRevisionSectionController implements DisposableView {
    private AuditViewModel viewModel;

    @FXML private TextField issueNumberField;
    @FXML private DateTimeField issueDateField;
    @FXML private TextField revisionNumberField;
    @FXML private DateTimeField revisionDateField;
    @FXML private ComboBox<String> revisionTypeComboBox;
    @FXML private CheckBox temporaryRevisionCheckBox;
    @FXML private TextField issueRevisionTitleField;
    @FXML private TextArea revisionReasonField;
    @FXML private TextArea reasonForIssueField;
    @FXML private TextArea summaryOfChangeRemarksField;
    @FXML private TextArea issueNotesField;
    @FXML private TextArea revisionNotesField;
    @FXML private TextField revisionLinkIdField;

    public void bindTo(AuditViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        unbindCurrentViewModel();
        this.viewModel = viewModel;
        for (var field : java.util.List.of(reasonForIssueField, summaryOfChangeRemarksField, issueNotesField, revisionNotesField, revisionLinkIdField)) {
            field.getParent().setVisible(viewModel.extendedRevisionControlsAvailableProperty().get());
            field.getParent().setManaged(viewModel.extendedRevisionControlsAvailableProperty().get());
        }
        revisionTypeComboBox.setItems(viewModel.revisionTypes());
        issueNumberField.textProperty().bindBidirectional(viewModel.issueNumberProperty());
        issueDateField.valueProperty().bindBidirectional(viewModel.issueDateProperty());
        revisionNumberField.textProperty().bindBidirectional(viewModel.revisionNumberProperty());
        revisionDateField.valueProperty().bindBidirectional(viewModel.revisionDateProperty());
        revisionTypeComboBox.valueProperty().bindBidirectional(viewModel.revisionTypeProperty());
        temporaryRevisionCheckBox.selectedProperty().bindBidirectional(viewModel.temporaryRevisionProperty());
        issueRevisionTitleField.textProperty().bindBidirectional(viewModel.issueRevisionTitleProperty());
        revisionReasonField.textProperty().bindBidirectional(viewModel.revisionReasonProperty());
        reasonForIssueField.textProperty().bindBidirectional(viewModel.reasonForIssueProperty());
        summaryOfChangeRemarksField.textProperty().bindBidirectional(viewModel.summaryOfChangeRemarksProperty());
        issueNotesField.textProperty().bindBidirectional(viewModel.issueNotesProperty());
        revisionNotesField.textProperty().bindBidirectional(viewModel.revisionNotesProperty());
        revisionLinkIdField.textProperty().bind(viewModel.revisionLinkIdProperty());
    }

    private void unbindCurrentViewModel() {
        if (viewModel == null) {
            return;
        }
        issueNumberField.textProperty().unbindBidirectional(viewModel.issueNumberProperty());
        issueDateField.valueProperty().unbindBidirectional(viewModel.issueDateProperty());
        revisionNumberField.textProperty().unbindBidirectional(viewModel.revisionNumberProperty());
        revisionDateField.valueProperty().unbindBidirectional(viewModel.revisionDateProperty());
        revisionTypeComboBox.valueProperty().unbindBidirectional(viewModel.revisionTypeProperty());
        temporaryRevisionCheckBox.selectedProperty().unbindBidirectional(viewModel.temporaryRevisionProperty());
        issueRevisionTitleField.textProperty().unbindBidirectional(viewModel.issueRevisionTitleProperty());
        revisionReasonField.textProperty().unbindBidirectional(viewModel.revisionReasonProperty());
        reasonForIssueField.textProperty().unbindBidirectional(viewModel.reasonForIssueProperty());
        summaryOfChangeRemarksField.textProperty().unbindBidirectional(viewModel.summaryOfChangeRemarksProperty());
        issueNotesField.textProperty().unbindBidirectional(viewModel.issueNotesProperty());
        revisionNotesField.textProperty().unbindBidirectional(viewModel.revisionNotesProperty());
        revisionLinkIdField.textProperty().unbind();
    }

    @Override
    public void disposeView() {
        unbindCurrentViewModel();
        viewModel = null;
    }
}

Links

Effectivity

Effectivity / Main

GUI review pending

Effectivity details.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Main
Shared form reference · 2026-10-10T12:32:19.516686 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Main
Static JavaFX layout · 1280 × 514 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (9)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Owner Scope
scopeComboBox
ComboBox
scopeProperty
Review requiredness, validation and display.
Effectivity Code
codeField
TextField
codeProperty
Review requiredness, validation and display.
Title
titleField
TextField
titleProperty
Review requiredness, validation and display.
Target Type
targetTypeComboBox
ComboBox
targetTypeProperty
Review requiredness, validation and display.
Node Type
nodeTypeComboBox
ComboBox
nodeTypeProperty
Review requiredness, validation and display.
Sequence No
sequenceNumberField
TextField
sequenceNumberProperty
Review requiredness, validation and display.
parent Effectivity
parentEffectivityLookup
UUID search lookup
parentEffectivityIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
learning Activity
learningActivityLookup
UUID search lookup
learningActivityIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
training Need Analysis
trainingNeedAnalysisLookup
UUID search lookup
trainingNeedAnalysisIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Source evidence

Tab key: effectivity--main
Section: Entire form
FXML SHA-256: 27b9532e355db4330843ba8d769b31fe19bdb085e63ae73de3c78b9123279064

coflyr-fe/src/main/resources/com/coflyr/fe/shared/effectivity/views/effectivity-main-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      styleClass="standard-section-flat"
      VBox.vgrow="ALWAYS">
    <children>
        <Label styleClass="section-title" text="Identity and Classification"/>
        <Label fx:id="editorTypeHint" visible="false" managed="false" wrapText="true" styleClass="field-hint"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox fx:id="scopeGroup" managed="false" visible="false" styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="2"><children><Label styleClass="field-label" text="Owner Scope"/><ComboBox fx:id="scopeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0"><children><Label styleClass="field-label" text="Effectivity Code"/><TextField fx:id="codeField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="0"><children><Label styleClass="field-label" text="Title"/><TextField fx:id="titleField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="1"><children><Label fx:id="targetTypeLabel" styleClass="field-label" text="Target Type"/><ComboBox fx:id="targetTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="2"><children><Label styleClass="field-label" text="Node Type"/><ComboBox fx:id="nodeTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="2"><children><Label styleClass="field-label" text="Sequence No"/><TextField fx:id="sequenceNumberField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
        <Label styleClass="section-title" text="Owner Links"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox fx:id="parentEffectivityLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <fx:include fx:id="parentEffectivityLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
                <VBox styleClass="field-group" GridPane.rowIndex="1" GridPane.columnSpan="3">
                    <children><fx:include fx:id="learningActivityLookup" source="../../../views/controls/db-search-lookup-field.fxml"/></children>
                </VBox>
                <VBox styleClass="field-group" GridPane.rowIndex="2" GridPane.columnSpan="3">
                    <children><fx:include fx:id="trainingNeedAnalysisLookup" source="../../../views/controls/db-search-lookup-field.fxml"/></children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController
package com.coflyr.fe.shared.effectivity.controllers;

import com.coflyr.fe.shared.effectivity.viewmodels.EffectivityViewModel;
import com.coflyr.fe.shared.effectivity.EffectivityEditorTypes;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.beans.value.ChangeListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.util.StringConverter;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EffectivitySectionController implements com.coflyr.fe.config.DisposableView {
    private EffectivityViewModel viewModel;
    private boolean bound;
    private boolean typeDriven;
    private ChangeListener<String> editorTypeListener;
    @FXML private Label targetTypeLabel;
    @FXML private Label editorTypeHint;
    @FXML private Label editorSectionTitle;
    private ChangeListener<String> scopeVisibilityListener;
    private ChangeListener<String> parentEffectivityListener;
    private ChangeListener<String> itemTypeListener;
    private ChangeListener<String> serialSelectionListener;
    @FXML private Node serialNumberTypeGroup;
    @FXML private Node rangeShiftGroup;
    @FXML private ComboBox<String> itemTypeComboBox;
    @FXML private Node softwareLookupGroup;

    @FXML private ToggleButton mainTabButton;
    @FXML private ToggleButton partSerialTabButton;
    @FXML private ToggleButton operatorTabButton;
    @FXML private ToggleButton validityTabButton;
    @FXML private ToggleButton remarksTabButton;
    @FXML private ToggleButton auditTabButton;
    @FXML private Node mainTabContent;
    @FXML private Node partSerialTabContent;
    @FXML private Node operatorTabContent;
    @FXML private Node validityTabContent;
    @FXML private Node remarksTabContent;
    @FXML private Node auditTabContent;
    @FXML private ComboBox<String> scopeComboBox;
    @FXML private Node parentEffectivityLookupGroup;
    @FXML private Node maintenanceTaskLookupGroup;
    @FXML private Node taskCardLookupGroup;
    @FXML private Node requirementLookupGroup;
    @FXML private Node maintenanceProgramLookupGroup;
    @FXML private Node deferredMaintenanceLookupGroup;
    @FXML private Node publicationLookupGroup;
    @FXML private Node configurationLookupGroup;
    @FXML private Node configurationMasterLookupGroup;
    @FXML private DbSearchLookupFieldController parentEffectivityLookupController;
    @FXML private TextField codeField;
    @FXML private TextField titleField;
    @FXML private ComboBox<String> relationshipTypeComboBox;
    @FXML private ComboBox<String> targetTypeComboBox;
    @FXML private ComboBox<String> nodeTypeComboBox;
    @FXML private TextField sequenceNumberField;
    @FXML private DbSearchLookupFieldController maintenanceTaskLookupController;
    @FXML private DbSearchLookupFieldController taskCardLookupController;
    @FXML private DbSearchLookupFieldController requirementLookupController;
    @FXML private DbSearchLookupFieldController maintenanceProgramMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredMaintenanceMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredDefectMasterLookupController;
    @FXML private DbSearchLookupFieldController learningActivityLookupController;
    @FXML private DbSearchLookupFieldController trainingNeedAnalysisLookupController;
    @FXML private DbSearchLookupFieldController publicationMasterLookupController;
    @FXML private DbSearchLookupFieldController configurationLookupController;
    @FXML private DbSearchLookupFieldController configurationMasterLookupController;
    @FXML private ComboBox<String> serialTypeComboBox;
    @FXML private ComboBox<String> serialNumberTypeComboBox;
    @FXML private TextField serialPrefixField;
    @FXML private TextField serialFromField;
    @FXML private TextField serialToField;
    @FXML private ComboBox<String> rangeShiftComboBox;
    @FXML private Node partMasterLookupGroup;
    @FXML private Node partBomLookupGroup;
    @FXML private Node rotableLookupGroup;
    @FXML private Node rotableSelectionModeGroup;
    @FXML private CheckBox chooseRotableCheckBox;
    @FXML private CheckBox choosePartSerialCheckBox;
    @FXML private Node serialTypeGroup;
    @FXML private Node serialPrefixGroup;
    @FXML private Node serialFromGroup;
    @FXML private Node serialToGroup;
    @FXML private Node serialNumberGroup;
    @FXML private Node rangeRemarksGroup;
    @FXML private DbSearchLookupFieldController partMasterLookupController;
    @FXML private DbSearchLookupFieldController partBomLookupController;
    @FXML private DbSearchLookupFieldController rotableLookupController;
    @FXML private DbSearchLookupFieldController operationSpecificationLookupController;
    @FXML private TextField rangeRemarksField;
    @FXML private TextField serialNumberField;
    @FXML private CheckBox appliesToFlightOperationsCheckBox;
    @FXML private CheckBox appliesToGroundOperationsCheckBox;
    @FXML private CheckBox appliesToLineMaintenanceCheckBox;
    @FXML private CheckBox appliesToBaseMaintenanceCheckBox;
    @FXML private CheckBox appliesToComponentMaintenanceCheckBox;
    @FXML private TextField startDateField;
    @FXML private TextField endDateField;

    public EffectivitySectionController(EffectivityViewModel viewModel) {
        this.viewModel = viewModel;
    }

    public void setTypeDriven(boolean typeDriven) {
        this.typeDriven = typeDriven;
    }

    @Override public void disposeView() {
        if (bound) unbindCurrentViewModel();
        bound = false;
    }

    @FXML
    private void initialize() {
        if (mainTabButton != null) {
            configureTabs();
        }
        configureRotableSelectionMode();
    }

    public void bindTo(EffectivityViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        if (bound && viewModel == this.viewModel) {
            return;
        }
        if (bound) unbindCurrentViewModel();
        this.viewModel = viewModel;
        bound = true;

        setItems(scopeComboBox, viewModel.scopes());
        setItems(relationshipTypeComboBox, viewModel.relationshipTypes());
        setItems(targetTypeComboBox, viewModel.targetTypes());
        if (typeDriven && targetTypeComboBox != null) {
            setItems(targetTypeComboBox, EffectivityEditorTypes.TYPES);
            targetTypeComboBox.setConverter(new StringConverter<>() {
                @Override public String toString(String value) { return EffectivityEditorTypes.label(value); }
                @Override public String fromString(String label) {
                    return EffectivityEditorTypes.TYPES.stream()
                            .filter(type -> EffectivityEditorTypes.label(type).equals(label))
                            .findFirst().orElse(label);
                }
            });
        }
        setItems(nodeTypeComboBox, viewModel.nodeTypes());
        setItems(serialTypeComboBox, viewModel.serialTypes());
        setItems(serialNumberTypeComboBox, viewModel.serialNumberTypes());
        setItems(rangeShiftComboBox, viewModel.rangeShifts());

        bind(scopeComboBox, viewModel.scopeProperty());
        if (scopeComboBox != null) scopeComboBox.setDisable(true);
        bind(codeField, viewModel.codeProperty());
        bindLookup(deferredDefectMasterLookupController, "deferred-defects", "Deferred Defect",
                "Search deferred defect", viewModel.deferredDefectMasterIdProperty(), viewModel.deferredDefectLookupQueryProperty());
        bindLookup(learningActivityLookupController, "learningactivity", "Learning Activity",
                "Search learning activity", viewModel.learningActivityIdProperty(), viewModel.learningActivityLookupQueryProperty());
        bindLookup(trainingNeedAnalysisLookupController, "trainingneedanalysis", "Training Need Analysis",
                "Search training need analysis", viewModel.trainingNeedAnalysisIdProperty(), viewModel.trainingNeedAnalysisLookupQueryProperty());
        bind(titleField, viewModel.titleProperty());
        bindLookup(parentEffectivityLookupController, "effectivity", "Existing Effectivity", "Search existing effectivity",
                viewModel.parentEffectivityIdProperty(), viewModel.parentEffectivityLookupQueryProperty());
        parentEffectivityListener = (observable, oldValue, newValue) -> {
            if (newValue != null && !newValue.isBlank()) {
                viewModel.nodeTypeProperty().set("LinkedObject");
            }
        };
        viewModel.parentEffectivityIdProperty().addListener(parentEffectivityListener);
        if (!viewModel.parentEffectivityIdProperty().get().isBlank()) {
            viewModel.nodeTypeProperty().set("LinkedObject");
        }
        bind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        bind(targetTypeComboBox, viewModel.targetTypeProperty());
        if (itemTypeComboBox != null) {
            setItems(itemTypeComboBox, java.util.List.of("Rotable", "Aircraft", "Component", "Part", "Hardware", "Consumable",
                    "Expendable", "Repairable", "Tool", "RawMaterial", "OtherMaterial", "Software"));
            bind(itemTypeComboBox, viewModel.targetTypeProperty());
            itemTypeListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.targetTypeProperty().addListener(itemTypeListener);
            applyItemTypeVisibility();
        }
        bind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        bind(sequenceNumberField, viewModel.sequenceNumberProperty());
        bindLookup(maintenanceTaskLookupController, "maintenance-tasks", "Maintenance Task", "Search maintenance task",
                viewModel.maintenanceTaskIdProperty(), viewModel.maintenanceTaskLookupQueryProperty());
        bindLookup(taskCardLookupController, "task-cards", "Task Card", "Search task card",
                viewModel.taskCardIdProperty(), viewModel.taskCardLookupQueryProperty());
        bindLookup(requirementLookupController, "requirements", "Requirement", "Search requirement",
                viewModel.requirementIdProperty(), viewModel.requirementLookupQueryProperty());
        bindLookup(maintenanceProgramMasterLookupController, "maintenance-programs", "Maintenance Program",
                "Search maintenance program", viewModel.maintenanceProgramMasterIdProperty(),
                viewModel.maintenanceProgramMasterLookupQueryProperty());
        bindLookup(deferredMaintenanceMasterLookupController, "deferred-maintenance", "Deferred Maintenance",
                "Search deferred maintenance", viewModel.deferredMaintenanceMasterIdProperty(),
                viewModel.deferredMaintenanceMasterLookupQueryProperty());
        bindLookup(publicationMasterLookupController, "publications", "Publication", "Search publication",
                viewModel.publicationMasterIdProperty(), viewModel.publicationMasterLookupQueryProperty());
        bindLookup(configurationLookupController, "configurations", "Configuration", "Search configuration",
                viewModel.configurationIdProperty(), viewModel.configurationLookupQueryProperty());
        bindLookup(configurationMasterLookupController, "configuration-masters", "Configuration Master",
                "Search configuration master", viewModel.configurationMasterIdProperty(),
                viewModel.configurationMasterLookupQueryProperty());
        bind(serialTypeComboBox, viewModel.serialTypeProperty());
        if (itemTypeComboBox != null) {
            serialSelectionListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.serialTypeProperty().addListener(serialSelectionListener);
        }
        bind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        bind(serialPrefixField, viewModel.serialPrefixProperty());
        bind(serialFromField, viewModel.serialFromProperty());
        bind(serialToField, viewModel.serialToProperty());
        bind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        bindLookup(partMasterLookupController, "parts", "Part Master", "Search part master",
                viewModel.partMasterIdProperty(), viewModel.partMasterLookupQueryProperty());
        bindLookup(partBomLookupController, "part-boms", "Part BOM", "Search part BOM",
                viewModel.partBomIdProperty(), viewModel.partBomLookupQueryProperty());
        bindLookup(rotableLookupController, "rotables", "Rotable", "Search rotable",
                viewModel.rotableIdProperty(), viewModel.rotableLookupQueryProperty());
        bindLookup(operationSpecificationLookupController, "operation-specifications", "Operations",
                "Search operations", viewModel.operationSpecificationIdProperty(),
                viewModel.operationSpecificationLookupQueryProperty());
        bind(rangeRemarksField, viewModel.rangeRemarksProperty());
        bind(serialNumberField, viewModel.serialNumberProperty());
        bind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        bind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        bind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        bind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        bind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        bind(startDateField, viewModel.startDateProperty());
        bind(endDateField, viewModel.endDateProperty());
        scopeVisibilityListener = (observable, oldScope, newScope) -> applyVisibility();
        viewModel.scopeProperty().addListener(scopeVisibilityListener);
        selectRotableModeFromCurrentModel();
        applyVisibility();
        if (typeDriven) {
            editorTypeListener = (observable, oldType, newType) -> applyEditorType();
            viewModel.targetTypeProperty().addListener(editorTypeListener);
            applyEditorType();
        }
    }

    private void applyEditorType() {
        String type = viewModel.targetTypeProperty().get();
        if (targetTypeLabel != null) targetTypeLabel.setText("Effectivity Type");
        if (targetTypeComboBox != null && type != null && !type.isBlank()
                && !targetTypeComboBox.getItems().contains(type)) targetTypeComboBox.getItems().add(type);
        if (editorTypeHint != null) {
            setVisible(editorTypeHint, true);
            editorTypeHint.setText("main".equals(EffectivityEditorTypes.tabFor(type))
                    ? "Choose the subject of this effectivity. Its matching editor opens automatically. Business Partner restrictions are maintained in Links."
                    : EffectivityEditorTypes.label(type) + " Effectivity is ready in the matching tab. Validity, Remarks, Audit and Links remain shared. Switching types preserves entered values; review existing criteria before saving.");
        }
        if (editorSectionTitle != null) editorSectionTitle.setText(EffectivityEditorTypes.label(type) + " Effectivity");
        setVisible(rotableSelectionModeGroup, false);
    }

    @FXML private void onMainTabClicked() { showTab(mainTabContent); }
    @FXML private void onPartSerialTabClicked() { showTab(partSerialTabContent); }
    @FXML private void onOperatorTabClicked() { showTab(operatorTabContent); }
    @FXML private void onValidityTabClicked() { showTab(validityTabContent); }
    @FXML private void onRemarksTabClicked() { showTab(remarksTabContent); }
    @FXML private void onAuditTabClicked() { showTab(auditTabContent); }

    private void configureTabs() {
        ToggleGroup group = new ToggleGroup();
        for (ToggleButton button : new ToggleButton[]{mainTabButton, partSerialTabButton, operatorTabButton, validityTabButton, remarksTabButton, auditTabButton}) {
            if (button != null) {
                button.setToggleGroup(group);
            }
        }
        group.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
            if (newToggle == null && oldToggle != null) {
                oldToggle.setSelected(true);
            }
        });
        mainTabButton.setSelected(true);
        showTab(mainTabContent);
    }

    private void showTab(Node selectedContent) {
        for (Node content : new Node[]{mainTabContent, partSerialTabContent, operatorTabContent, validityTabContent, remarksTabContent, auditTabContent}) {
            if (content == null) {
                continue;
            }
            boolean selected = content == selectedContent;
            content.setManaged(selected);
            content.setVisible(selected);
        }
    }

    private void unbindCurrentViewModel() {
        if (editorTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(editorTypeListener);
            editorTypeListener = null;
        }
        if (serialSelectionListener != null) {
            viewModel.serialTypeProperty().removeListener(serialSelectionListener);
            serialSelectionListener = null;
        }
        if (itemTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(itemTypeListener);
            itemTypeListener = null;
        }
        unbind(itemTypeComboBox, viewModel.targetTypeProperty());
        if (scopeVisibilityListener != null) {
            viewModel.scopeProperty().removeListener(scopeVisibilityListener);
            scopeVisibilityListener = null;
        }
        if (parentEffectivityListener != null) {
            viewModel.parentEffectivityIdProperty().removeListener(parentEffectivityListener);
            parentEffectivityListener = null;
        }
        unbind(scopeComboBox, viewModel.scopeProperty());
        unbind(codeField, viewModel.codeProperty());
        unbind(titleField, viewModel.titleProperty());
        unbind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        unbind(targetTypeComboBox, viewModel.targetTypeProperty());
        unbind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        unbind(sequenceNumberField, viewModel.sequenceNumberProperty());
        unbind(serialTypeComboBox, viewModel.serialTypeProperty());
        unbind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        unbind(serialPrefixField, viewModel.serialPrefixProperty());
        unbind(serialFromField, viewModel.serialFromProperty());
        unbind(serialToField, viewModel.serialToProperty());
        unbind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        unbind(rangeRemarksField, viewModel.rangeRemarksProperty());
        unbind(serialNumberField, viewModel.serialNumberProperty());
        unbind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        unbind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        unbind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        unbind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        unbind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        unbind(startDateField, viewModel.startDateProperty());
        unbind(endDateField, viewModel.endDateProperty());
    }

    private void applyVisibility() {
        for (Node node : new Node[]{
                parentEffectivityLookupGroup, maintenanceTaskLookupGroup, taskCardLookupGroup,
                requirementLookupGroup, maintenanceProgramLookupGroup, deferredMaintenanceLookupGroup,
                publicationLookupGroup, configurationLookupGroup, configurationMasterLookupGroup,
                partMasterLookupGroup, partBomLookupGroup, rotableLookupGroup,
                rotableSelectionModeGroup, serialTypeGroup, serialPrefixGroup, serialFromGroup, serialToGroup,
                serialNumberGroup, rangeRemarksGroup
        }) {
            setVisible(node, true);
        }
        applyRotableSelectionMode();
        applyItemTypeVisibility();
        if (typeDriven) applyEditorType();
    }

    private void applyItemTypeVisibility() {
        if (itemTypeComboBox == null) return;
        String type = viewModel.targetTypeProperty().get();
        boolean software = "Software".equals(type);
        boolean rotable = java.util.List.of("Rotable", "Aircraft", "Component").contains(type == null ? "" : type);
        boolean part = itemTypeComboBox.getItems().contains(type) && !software && !rotable;
        if (software && configurationLookupController != null) {
            configurationLookupController.configure("configurations", "Software Configuration",
                    "Search software configuration", java.util.List.of(
                            new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                                    "Functional Position Type", "E", "Software")));
        }
        if (part && partMasterLookupController != null) {
            String material = "OtherMaterial".equals(type) ? "Other" : type;
            boolean filter = !java.util.List.of("Part", "Hardware").contains(type);
            partMasterLookupController.configure("parts", "Part Master", "Search " + type,
                    filter ? java.util.List.of(new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                            "Material Class", "E", material)) : java.util.List.of());
        }
        setVisible(softwareLookupGroup, software);
        setVisible(rotableLookupGroup, rotable);
        setVisible(partMasterLookupGroup, part);
        boolean serials = part || rotable;
        String selection = viewModel.serialTypeProperty().get();
        boolean range = serials && "Range".equals(selection);
        setVisible(serialTypeGroup, serials);
        setVisible(serialNumberTypeGroup, serials);
        setVisible(serialNumberGroup, serials && ("Single".equals(selection) || "Pattern".equals(selection)));
        setVisible(serialPrefixGroup, serials);
        setVisible(serialFromGroup, range);
        setVisible(serialToGroup, range);
        setVisible(rangeShiftGroup, range);
        setVisible(rangeRemarksGroup, serials);
    }

    private void configureRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        chooseRotableCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                choosePartSerialCheckBox.setSelected(false);
            } else if (!choosePartSerialCheckBox.isSelected()) {
                chooseRotableCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        choosePartSerialCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                chooseRotableCheckBox.setSelected(false);
            } else if (!chooseRotableCheckBox.isSelected()) {
                choosePartSerialCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        chooseRotableCheckBox.setSelected(true);
        applyRotableSelectionMode();
    }

    private void selectRotableModeFromCurrentModel() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null || viewModel == null) {
            return;
        }
        boolean hasRotable = hasText(viewModel.rotableIdProperty().get()) || hasText(viewModel.rotableLookupQueryProperty().get());
        boolean hasPartSerial = hasText(viewModel.partMasterIdProperty().get())
                || hasText(viewModel.partMasterLookupQueryProperty().get())
                || hasText(viewModel.serialNumberProperty().get())
                || hasText(viewModel.serialPrefixProperty().get())
                || hasText(viewModel.serialFromProperty().get())
                || hasText(viewModel.serialToProperty().get())
                || hasText(viewModel.rangeRemarksProperty().get());
        choosePartSerialCheckBox.setSelected(!hasRotable && hasPartSerial);
        chooseRotableCheckBox.setSelected(hasRotable || !hasPartSerial);
    }

    private void applyRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        boolean chooseRotable = chooseRotableCheckBox.isSelected();
        setVisible(rotableLookupGroup, chooseRotable);
        setVisible(partMasterLookupGroup, !chooseRotable);
        setVisible(serialTypeGroup, !chooseRotable);
        setVisible(serialNumberGroup, !chooseRotable);
        setVisible(serialPrefixGroup, !chooseRotable);
        setVisible(serialFromGroup, !chooseRotable);
        setVisible(serialToGroup, !chooseRotable);
        setVisible(rangeRemarksGroup, !chooseRotable);
    }

    private boolean hasText(String value) {
        return value != null && !value.isBlank();
    }

    private void setVisible(Node node, boolean visible) {
        if (node == null) {
            return;
        }
        node.setVisible(visible);
        node.setManaged(visible);
    }

    private void bind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().bindBidirectional(property);
        }
    }

    private void unbind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().unbindBidirectional(property);
        }
    }

    private void bind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().bindBidirectional(property);
        }
    }

    private void unbind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().unbindBidirectional(property);
        }
    }

    private void bind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().bindBidirectional(property);
        }
    }

    private void unbind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().unbindBidirectional(property);
        }
    }

    private void setItems(ComboBox<String> control, java.util.List<String> items) {
        if (control != null) {
            control.getItems().setAll(items);
        }
    }

    private void bindLookup(
            DbSearchLookupFieldController controller,
            String definitionKey,
            String label,
            String prompt,
            StringProperty selectedId,
            StringProperty query
    ) {
        if (controller != null) {
            LookupFieldBinding.bind(controller, definitionKey, label, prompt, selectedId, query);
        }
    }
}

Effectivity / Maintenance

GUI review pending

Maintenance program, task, and task card links.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Maintenance
Shared form reference · 2026-10-10T12:33:40.568231 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Maintenance
Static JavaFX layout · 1280 × 390 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (4)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Relationship Type
relationshipTypeComboBox
ComboBox
relationshipTypeProperty
Review requiredness, validation and display.
maintenance Program Master
maintenanceProgramMasterLookup
UUID search lookup
maintenanceProgramMasterIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
maintenance Task
maintenanceTaskLookup
UUID search lookup
maintenanceTaskIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
task Card
taskCardLookup
UUID search lookup
taskCardIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Source evidence

Tab key: effectivity--maintenance
Section: Entire form
FXML SHA-256: 1304a2da4a79806fb8d1f1604fd1c36c39f5f3d80ff393430a139346e502ae37

coflyr-fe/src/main/resources/com/coflyr/fe/shared/effectivity/views/effectivity-maintenance-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      styleClass="standard-section-flat"
      VBox.vgrow="ALWAYS">
    <children>
        <Label fx:id="editorSectionTitle" styleClass="section-title" text="Maintenance"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Relationship Type"/>
                        <ComboBox fx:id="relationshipTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="maintenanceProgramLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <fx:include fx:id="maintenanceProgramMasterLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
                <VBox fx:id="maintenanceTaskLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2">
                    <children>
                        <fx:include fx:id="maintenanceTaskLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
                <VBox fx:id="taskCardLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3">
                    <children>
                        <fx:include fx:id="taskCardLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController
package com.coflyr.fe.shared.effectivity.controllers;

import com.coflyr.fe.shared.effectivity.viewmodels.EffectivityViewModel;
import com.coflyr.fe.shared.effectivity.EffectivityEditorTypes;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.beans.value.ChangeListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.util.StringConverter;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EffectivitySectionController implements com.coflyr.fe.config.DisposableView {
    private EffectivityViewModel viewModel;
    private boolean bound;
    private boolean typeDriven;
    private ChangeListener<String> editorTypeListener;
    @FXML private Label targetTypeLabel;
    @FXML private Label editorTypeHint;
    @FXML private Label editorSectionTitle;
    private ChangeListener<String> scopeVisibilityListener;
    private ChangeListener<String> parentEffectivityListener;
    private ChangeListener<String> itemTypeListener;
    private ChangeListener<String> serialSelectionListener;
    @FXML private Node serialNumberTypeGroup;
    @FXML private Node rangeShiftGroup;
    @FXML private ComboBox<String> itemTypeComboBox;
    @FXML private Node softwareLookupGroup;

    @FXML private ToggleButton mainTabButton;
    @FXML private ToggleButton partSerialTabButton;
    @FXML private ToggleButton operatorTabButton;
    @FXML private ToggleButton validityTabButton;
    @FXML private ToggleButton remarksTabButton;
    @FXML private ToggleButton auditTabButton;
    @FXML private Node mainTabContent;
    @FXML private Node partSerialTabContent;
    @FXML private Node operatorTabContent;
    @FXML private Node validityTabContent;
    @FXML private Node remarksTabContent;
    @FXML private Node auditTabContent;
    @FXML private ComboBox<String> scopeComboBox;
    @FXML private Node parentEffectivityLookupGroup;
    @FXML private Node maintenanceTaskLookupGroup;
    @FXML private Node taskCardLookupGroup;
    @FXML private Node requirementLookupGroup;
    @FXML private Node maintenanceProgramLookupGroup;
    @FXML private Node deferredMaintenanceLookupGroup;
    @FXML private Node publicationLookupGroup;
    @FXML private Node configurationLookupGroup;
    @FXML private Node configurationMasterLookupGroup;
    @FXML private DbSearchLookupFieldController parentEffectivityLookupController;
    @FXML private TextField codeField;
    @FXML private TextField titleField;
    @FXML private ComboBox<String> relationshipTypeComboBox;
    @FXML private ComboBox<String> targetTypeComboBox;
    @FXML private ComboBox<String> nodeTypeComboBox;
    @FXML private TextField sequenceNumberField;
    @FXML private DbSearchLookupFieldController maintenanceTaskLookupController;
    @FXML private DbSearchLookupFieldController taskCardLookupController;
    @FXML private DbSearchLookupFieldController requirementLookupController;
    @FXML private DbSearchLookupFieldController maintenanceProgramMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredMaintenanceMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredDefectMasterLookupController;
    @FXML private DbSearchLookupFieldController learningActivityLookupController;
    @FXML private DbSearchLookupFieldController trainingNeedAnalysisLookupController;
    @FXML private DbSearchLookupFieldController publicationMasterLookupController;
    @FXML private DbSearchLookupFieldController configurationLookupController;
    @FXML private DbSearchLookupFieldController configurationMasterLookupController;
    @FXML private ComboBox<String> serialTypeComboBox;
    @FXML private ComboBox<String> serialNumberTypeComboBox;
    @FXML private TextField serialPrefixField;
    @FXML private TextField serialFromField;
    @FXML private TextField serialToField;
    @FXML private ComboBox<String> rangeShiftComboBox;
    @FXML private Node partMasterLookupGroup;
    @FXML private Node partBomLookupGroup;
    @FXML private Node rotableLookupGroup;
    @FXML private Node rotableSelectionModeGroup;
    @FXML private CheckBox chooseRotableCheckBox;
    @FXML private CheckBox choosePartSerialCheckBox;
    @FXML private Node serialTypeGroup;
    @FXML private Node serialPrefixGroup;
    @FXML private Node serialFromGroup;
    @FXML private Node serialToGroup;
    @FXML private Node serialNumberGroup;
    @FXML private Node rangeRemarksGroup;
    @FXML private DbSearchLookupFieldController partMasterLookupController;
    @FXML private DbSearchLookupFieldController partBomLookupController;
    @FXML private DbSearchLookupFieldController rotableLookupController;
    @FXML private DbSearchLookupFieldController operationSpecificationLookupController;
    @FXML private TextField rangeRemarksField;
    @FXML private TextField serialNumberField;
    @FXML private CheckBox appliesToFlightOperationsCheckBox;
    @FXML private CheckBox appliesToGroundOperationsCheckBox;
    @FXML private CheckBox appliesToLineMaintenanceCheckBox;
    @FXML private CheckBox appliesToBaseMaintenanceCheckBox;
    @FXML private CheckBox appliesToComponentMaintenanceCheckBox;
    @FXML private TextField startDateField;
    @FXML private TextField endDateField;

    public EffectivitySectionController(EffectivityViewModel viewModel) {
        this.viewModel = viewModel;
    }

    public void setTypeDriven(boolean typeDriven) {
        this.typeDriven = typeDriven;
    }

    @Override public void disposeView() {
        if (bound) unbindCurrentViewModel();
        bound = false;
    }

    @FXML
    private void initialize() {
        if (mainTabButton != null) {
            configureTabs();
        }
        configureRotableSelectionMode();
    }

    public void bindTo(EffectivityViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        if (bound && viewModel == this.viewModel) {
            return;
        }
        if (bound) unbindCurrentViewModel();
        this.viewModel = viewModel;
        bound = true;

        setItems(scopeComboBox, viewModel.scopes());
        setItems(relationshipTypeComboBox, viewModel.relationshipTypes());
        setItems(targetTypeComboBox, viewModel.targetTypes());
        if (typeDriven && targetTypeComboBox != null) {
            setItems(targetTypeComboBox, EffectivityEditorTypes.TYPES);
            targetTypeComboBox.setConverter(new StringConverter<>() {
                @Override public String toString(String value) { return EffectivityEditorTypes.label(value); }
                @Override public String fromString(String label) {
                    return EffectivityEditorTypes.TYPES.stream()
                            .filter(type -> EffectivityEditorTypes.label(type).equals(label))
                            .findFirst().orElse(label);
                }
            });
        }
        setItems(nodeTypeComboBox, viewModel.nodeTypes());
        setItems(serialTypeComboBox, viewModel.serialTypes());
        setItems(serialNumberTypeComboBox, viewModel.serialNumberTypes());
        setItems(rangeShiftComboBox, viewModel.rangeShifts());

        bind(scopeComboBox, viewModel.scopeProperty());
        if (scopeComboBox != null) scopeComboBox.setDisable(true);
        bind(codeField, viewModel.codeProperty());
        bindLookup(deferredDefectMasterLookupController, "deferred-defects", "Deferred Defect",
                "Search deferred defect", viewModel.deferredDefectMasterIdProperty(), viewModel.deferredDefectLookupQueryProperty());
        bindLookup(learningActivityLookupController, "learningactivity", "Learning Activity",
                "Search learning activity", viewModel.learningActivityIdProperty(), viewModel.learningActivityLookupQueryProperty());
        bindLookup(trainingNeedAnalysisLookupController, "trainingneedanalysis", "Training Need Analysis",
                "Search training need analysis", viewModel.trainingNeedAnalysisIdProperty(), viewModel.trainingNeedAnalysisLookupQueryProperty());
        bind(titleField, viewModel.titleProperty());
        bindLookup(parentEffectivityLookupController, "effectivity", "Existing Effectivity", "Search existing effectivity",
                viewModel.parentEffectivityIdProperty(), viewModel.parentEffectivityLookupQueryProperty());
        parentEffectivityListener = (observable, oldValue, newValue) -> {
            if (newValue != null && !newValue.isBlank()) {
                viewModel.nodeTypeProperty().set("LinkedObject");
            }
        };
        viewModel.parentEffectivityIdProperty().addListener(parentEffectivityListener);
        if (!viewModel.parentEffectivityIdProperty().get().isBlank()) {
            viewModel.nodeTypeProperty().set("LinkedObject");
        }
        bind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        bind(targetTypeComboBox, viewModel.targetTypeProperty());
        if (itemTypeComboBox != null) {
            setItems(itemTypeComboBox, java.util.List.of("Rotable", "Aircraft", "Component", "Part", "Hardware", "Consumable",
                    "Expendable", "Repairable", "Tool", "RawMaterial", "OtherMaterial", "Software"));
            bind(itemTypeComboBox, viewModel.targetTypeProperty());
            itemTypeListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.targetTypeProperty().addListener(itemTypeListener);
            applyItemTypeVisibility();
        }
        bind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        bind(sequenceNumberField, viewModel.sequenceNumberProperty());
        bindLookup(maintenanceTaskLookupController, "maintenance-tasks", "Maintenance Task", "Search maintenance task",
                viewModel.maintenanceTaskIdProperty(), viewModel.maintenanceTaskLookupQueryProperty());
        bindLookup(taskCardLookupController, "task-cards", "Task Card", "Search task card",
                viewModel.taskCardIdProperty(), viewModel.taskCardLookupQueryProperty());
        bindLookup(requirementLookupController, "requirements", "Requirement", "Search requirement",
                viewModel.requirementIdProperty(), viewModel.requirementLookupQueryProperty());
        bindLookup(maintenanceProgramMasterLookupController, "maintenance-programs", "Maintenance Program",
                "Search maintenance program", viewModel.maintenanceProgramMasterIdProperty(),
                viewModel.maintenanceProgramMasterLookupQueryProperty());
        bindLookup(deferredMaintenanceMasterLookupController, "deferred-maintenance", "Deferred Maintenance",
                "Search deferred maintenance", viewModel.deferredMaintenanceMasterIdProperty(),
                viewModel.deferredMaintenanceMasterLookupQueryProperty());
        bindLookup(publicationMasterLookupController, "publications", "Publication", "Search publication",
                viewModel.publicationMasterIdProperty(), viewModel.publicationMasterLookupQueryProperty());
        bindLookup(configurationLookupController, "configurations", "Configuration", "Search configuration",
                viewModel.configurationIdProperty(), viewModel.configurationLookupQueryProperty());
        bindLookup(configurationMasterLookupController, "configuration-masters", "Configuration Master",
                "Search configuration master", viewModel.configurationMasterIdProperty(),
                viewModel.configurationMasterLookupQueryProperty());
        bind(serialTypeComboBox, viewModel.serialTypeProperty());
        if (itemTypeComboBox != null) {
            serialSelectionListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.serialTypeProperty().addListener(serialSelectionListener);
        }
        bind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        bind(serialPrefixField, viewModel.serialPrefixProperty());
        bind(serialFromField, viewModel.serialFromProperty());
        bind(serialToField, viewModel.serialToProperty());
        bind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        bindLookup(partMasterLookupController, "parts", "Part Master", "Search part master",
                viewModel.partMasterIdProperty(), viewModel.partMasterLookupQueryProperty());
        bindLookup(partBomLookupController, "part-boms", "Part BOM", "Search part BOM",
                viewModel.partBomIdProperty(), viewModel.partBomLookupQueryProperty());
        bindLookup(rotableLookupController, "rotables", "Rotable", "Search rotable",
                viewModel.rotableIdProperty(), viewModel.rotableLookupQueryProperty());
        bindLookup(operationSpecificationLookupController, "operation-specifications", "Operations",
                "Search operations", viewModel.operationSpecificationIdProperty(),
                viewModel.operationSpecificationLookupQueryProperty());
        bind(rangeRemarksField, viewModel.rangeRemarksProperty());
        bind(serialNumberField, viewModel.serialNumberProperty());
        bind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        bind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        bind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        bind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        bind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        bind(startDateField, viewModel.startDateProperty());
        bind(endDateField, viewModel.endDateProperty());
        scopeVisibilityListener = (observable, oldScope, newScope) -> applyVisibility();
        viewModel.scopeProperty().addListener(scopeVisibilityListener);
        selectRotableModeFromCurrentModel();
        applyVisibility();
        if (typeDriven) {
            editorTypeListener = (observable, oldType, newType) -> applyEditorType();
            viewModel.targetTypeProperty().addListener(editorTypeListener);
            applyEditorType();
        }
    }

    private void applyEditorType() {
        String type = viewModel.targetTypeProperty().get();
        if (targetTypeLabel != null) targetTypeLabel.setText("Effectivity Type");
        if (targetTypeComboBox != null && type != null && !type.isBlank()
                && !targetTypeComboBox.getItems().contains(type)) targetTypeComboBox.getItems().add(type);
        if (editorTypeHint != null) {
            setVisible(editorTypeHint, true);
            editorTypeHint.setText("main".equals(EffectivityEditorTypes.tabFor(type))
                    ? "Choose the subject of this effectivity. Its matching editor opens automatically. Business Partner restrictions are maintained in Links."
                    : EffectivityEditorTypes.label(type) + " Effectivity is ready in the matching tab. Validity, Remarks, Audit and Links remain shared. Switching types preserves entered values; review existing criteria before saving.");
        }
        if (editorSectionTitle != null) editorSectionTitle.setText(EffectivityEditorTypes.label(type) + " Effectivity");
        setVisible(rotableSelectionModeGroup, false);
    }

    @FXML private void onMainTabClicked() { showTab(mainTabContent); }
    @FXML private void onPartSerialTabClicked() { showTab(partSerialTabContent); }
    @FXML private void onOperatorTabClicked() { showTab(operatorTabContent); }
    @FXML private void onValidityTabClicked() { showTab(validityTabContent); }
    @FXML private void onRemarksTabClicked() { showTab(remarksTabContent); }
    @FXML private void onAuditTabClicked() { showTab(auditTabContent); }

    private void configureTabs() {
        ToggleGroup group = new ToggleGroup();
        for (ToggleButton button : new ToggleButton[]{mainTabButton, partSerialTabButton, operatorTabButton, validityTabButton, remarksTabButton, auditTabButton}) {
            if (button != null) {
                button.setToggleGroup(group);
            }
        }
        group.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
            if (newToggle == null && oldToggle != null) {
                oldToggle.setSelected(true);
            }
        });
        mainTabButton.setSelected(true);
        showTab(mainTabContent);
    }

    private void showTab(Node selectedContent) {
        for (Node content : new Node[]{mainTabContent, partSerialTabContent, operatorTabContent, validityTabContent, remarksTabContent, auditTabContent}) {
            if (content == null) {
                continue;
            }
            boolean selected = content == selectedContent;
            content.setManaged(selected);
            content.setVisible(selected);
        }
    }

    private void unbindCurrentViewModel() {
        if (editorTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(editorTypeListener);
            editorTypeListener = null;
        }
        if (serialSelectionListener != null) {
            viewModel.serialTypeProperty().removeListener(serialSelectionListener);
            serialSelectionListener = null;
        }
        if (itemTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(itemTypeListener);
            itemTypeListener = null;
        }
        unbind(itemTypeComboBox, viewModel.targetTypeProperty());
        if (scopeVisibilityListener != null) {
            viewModel.scopeProperty().removeListener(scopeVisibilityListener);
            scopeVisibilityListener = null;
        }
        if (parentEffectivityListener != null) {
            viewModel.parentEffectivityIdProperty().removeListener(parentEffectivityListener);
            parentEffectivityListener = null;
        }
        unbind(scopeComboBox, viewModel.scopeProperty());
        unbind(codeField, viewModel.codeProperty());
        unbind(titleField, viewModel.titleProperty());
        unbind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        unbind(targetTypeComboBox, viewModel.targetTypeProperty());
        unbind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        unbind(sequenceNumberField, viewModel.sequenceNumberProperty());
        unbind(serialTypeComboBox, viewModel.serialTypeProperty());
        unbind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        unbind(serialPrefixField, viewModel.serialPrefixProperty());
        unbind(serialFromField, viewModel.serialFromProperty());
        unbind(serialToField, viewModel.serialToProperty());
        unbind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        unbind(rangeRemarksField, viewModel.rangeRemarksProperty());
        unbind(serialNumberField, viewModel.serialNumberProperty());
        unbind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        unbind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        unbind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        unbind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        unbind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        unbind(startDateField, viewModel.startDateProperty());
        unbind(endDateField, viewModel.endDateProperty());
    }

    private void applyVisibility() {
        for (Node node : new Node[]{
                parentEffectivityLookupGroup, maintenanceTaskLookupGroup, taskCardLookupGroup,
                requirementLookupGroup, maintenanceProgramLookupGroup, deferredMaintenanceLookupGroup,
                publicationLookupGroup, configurationLookupGroup, configurationMasterLookupGroup,
                partMasterLookupGroup, partBomLookupGroup, rotableLookupGroup,
                rotableSelectionModeGroup, serialTypeGroup, serialPrefixGroup, serialFromGroup, serialToGroup,
                serialNumberGroup, rangeRemarksGroup
        }) {
            setVisible(node, true);
        }
        applyRotableSelectionMode();
        applyItemTypeVisibility();
        if (typeDriven) applyEditorType();
    }

    private void applyItemTypeVisibility() {
        if (itemTypeComboBox == null) return;
        String type = viewModel.targetTypeProperty().get();
        boolean software = "Software".equals(type);
        boolean rotable = java.util.List.of("Rotable", "Aircraft", "Component").contains(type == null ? "" : type);
        boolean part = itemTypeComboBox.getItems().contains(type) && !software && !rotable;
        if (software && configurationLookupController != null) {
            configurationLookupController.configure("configurations", "Software Configuration",
                    "Search software configuration", java.util.List.of(
                            new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                                    "Functional Position Type", "E", "Software")));
        }
        if (part && partMasterLookupController != null) {
            String material = "OtherMaterial".equals(type) ? "Other" : type;
            boolean filter = !java.util.List.of("Part", "Hardware").contains(type);
            partMasterLookupController.configure("parts", "Part Master", "Search " + type,
                    filter ? java.util.List.of(new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                            "Material Class", "E", material)) : java.util.List.of());
        }
        setVisible(softwareLookupGroup, software);
        setVisible(rotableLookupGroup, rotable);
        setVisible(partMasterLookupGroup, part);
        boolean serials = part || rotable;
        String selection = viewModel.serialTypeProperty().get();
        boolean range = serials && "Range".equals(selection);
        setVisible(serialTypeGroup, serials);
        setVisible(serialNumberTypeGroup, serials);
        setVisible(serialNumberGroup, serials && ("Single".equals(selection) || "Pattern".equals(selection)));
        setVisible(serialPrefixGroup, serials);
        setVisible(serialFromGroup, range);
        setVisible(serialToGroup, range);
        setVisible(rangeShiftGroup, range);
        setVisible(rangeRemarksGroup, serials);
    }

    private void configureRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        chooseRotableCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                choosePartSerialCheckBox.setSelected(false);
            } else if (!choosePartSerialCheckBox.isSelected()) {
                chooseRotableCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        choosePartSerialCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                chooseRotableCheckBox.setSelected(false);
            } else if (!chooseRotableCheckBox.isSelected()) {
                choosePartSerialCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        chooseRotableCheckBox.setSelected(true);
        applyRotableSelectionMode();
    }

    private void selectRotableModeFromCurrentModel() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null || viewModel == null) {
            return;
        }
        boolean hasRotable = hasText(viewModel.rotableIdProperty().get()) || hasText(viewModel.rotableLookupQueryProperty().get());
        boolean hasPartSerial = hasText(viewModel.partMasterIdProperty().get())
                || hasText(viewModel.partMasterLookupQueryProperty().get())
                || hasText(viewModel.serialNumberProperty().get())
                || hasText(viewModel.serialPrefixProperty().get())
                || hasText(viewModel.serialFromProperty().get())
                || hasText(viewModel.serialToProperty().get())
                || hasText(viewModel.rangeRemarksProperty().get());
        choosePartSerialCheckBox.setSelected(!hasRotable && hasPartSerial);
        chooseRotableCheckBox.setSelected(hasRotable || !hasPartSerial);
    }

    private void applyRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        boolean chooseRotable = chooseRotableCheckBox.isSelected();
        setVisible(rotableLookupGroup, chooseRotable);
        setVisible(partMasterLookupGroup, !chooseRotable);
        setVisible(serialTypeGroup, !chooseRotable);
        setVisible(serialNumberGroup, !chooseRotable);
        setVisible(serialPrefixGroup, !chooseRotable);
        setVisible(serialFromGroup, !chooseRotable);
        setVisible(serialToGroup, !chooseRotable);
        setVisible(rangeRemarksGroup, !chooseRotable);
    }

    private boolean hasText(String value) {
        return value != null && !value.isBlank();
    }

    private void setVisible(Node node, boolean visible) {
        if (node == null) {
            return;
        }
        node.setVisible(visible);
        node.setManaged(visible);
    }

    private void bind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().bindBidirectional(property);
        }
    }

    private void unbind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().unbindBidirectional(property);
        }
    }

    private void bind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().bindBidirectional(property);
        }
    }

    private void unbind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().unbindBidirectional(property);
        }
    }

    private void bind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().bindBidirectional(property);
        }
    }

    private void unbind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().unbindBidirectional(property);
        }
    }

    private void setItems(ComboBox<String> control, java.util.List<String> items) {
        if (control != null) {
            control.getItems().setAll(items);
        }
    }

    private void bindLookup(
            DbSearchLookupFieldController controller,
            String definitionKey,
            String label,
            String prompt,
            StringProperty selectedId,
            StringProperty query
    ) {
        if (controller != null) {
            LookupFieldBinding.bind(controller, definitionKey, label, prompt, selectedId, query);
        }
    }
}

Effectivity / Deferred Maintenance

GUI review pending

Deferred maintenance link.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Deferred Maintenance
Shared form reference · 2026-10-10T12:34:07.608062 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Deferred Maintenance
Static JavaFX layout · 1280 × 320 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (3)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Relationship Type
relationshipTypeComboBox
ComboBox
relationshipTypeProperty
Review requiredness, validation and display.
deferred Maintenance Master
deferredMaintenanceMasterLookup
UUID search lookup
deferredMaintenanceMasterIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
deferred Defect Master
deferredDefectMasterLookup
UUID search lookup
deferredDefectMasterIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Source evidence

Tab key: effectivity--deferredmaintenance
Section: Entire form
FXML SHA-256: 6c4a0084c7cdab0ee256b714961ea8d690d594d3572feb168f7965a0e3a8d956

coflyr-fe/src/main/resources/com/coflyr/fe/shared/effectivity/views/effectivity-deferred-maintenance-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      styleClass="standard-section-flat"
      VBox.vgrow="ALWAYS">
    <children>
        <Label fx:id="editorSectionTitle" styleClass="section-title" text="Deferred Maintenance"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Relationship Type"/>
                        <ComboBox fx:id="relationshipTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="deferredMaintenanceLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <fx:include fx:id="deferredMaintenanceMasterLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
                <VBox styleClass="field-group" GridPane.rowIndex="2" GridPane.columnSpan="3">
                    <children><fx:include fx:id="deferredDefectMasterLookup" source="../../../views/controls/db-search-lookup-field.fxml"/></children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController
package com.coflyr.fe.shared.effectivity.controllers;

import com.coflyr.fe.shared.effectivity.viewmodels.EffectivityViewModel;
import com.coflyr.fe.shared.effectivity.EffectivityEditorTypes;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.beans.value.ChangeListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.util.StringConverter;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EffectivitySectionController implements com.coflyr.fe.config.DisposableView {
    private EffectivityViewModel viewModel;
    private boolean bound;
    private boolean typeDriven;
    private ChangeListener<String> editorTypeListener;
    @FXML private Label targetTypeLabel;
    @FXML private Label editorTypeHint;
    @FXML private Label editorSectionTitle;
    private ChangeListener<String> scopeVisibilityListener;
    private ChangeListener<String> parentEffectivityListener;
    private ChangeListener<String> itemTypeListener;
    private ChangeListener<String> serialSelectionListener;
    @FXML private Node serialNumberTypeGroup;
    @FXML private Node rangeShiftGroup;
    @FXML private ComboBox<String> itemTypeComboBox;
    @FXML private Node softwareLookupGroup;

    @FXML private ToggleButton mainTabButton;
    @FXML private ToggleButton partSerialTabButton;
    @FXML private ToggleButton operatorTabButton;
    @FXML private ToggleButton validityTabButton;
    @FXML private ToggleButton remarksTabButton;
    @FXML private ToggleButton auditTabButton;
    @FXML private Node mainTabContent;
    @FXML private Node partSerialTabContent;
    @FXML private Node operatorTabContent;
    @FXML private Node validityTabContent;
    @FXML private Node remarksTabContent;
    @FXML private Node auditTabContent;
    @FXML private ComboBox<String> scopeComboBox;
    @FXML private Node parentEffectivityLookupGroup;
    @FXML private Node maintenanceTaskLookupGroup;
    @FXML private Node taskCardLookupGroup;
    @FXML private Node requirementLookupGroup;
    @FXML private Node maintenanceProgramLookupGroup;
    @FXML private Node deferredMaintenanceLookupGroup;
    @FXML private Node publicationLookupGroup;
    @FXML private Node configurationLookupGroup;
    @FXML private Node configurationMasterLookupGroup;
    @FXML private DbSearchLookupFieldController parentEffectivityLookupController;
    @FXML private TextField codeField;
    @FXML private TextField titleField;
    @FXML private ComboBox<String> relationshipTypeComboBox;
    @FXML private ComboBox<String> targetTypeComboBox;
    @FXML private ComboBox<String> nodeTypeComboBox;
    @FXML private TextField sequenceNumberField;
    @FXML private DbSearchLookupFieldController maintenanceTaskLookupController;
    @FXML private DbSearchLookupFieldController taskCardLookupController;
    @FXML private DbSearchLookupFieldController requirementLookupController;
    @FXML private DbSearchLookupFieldController maintenanceProgramMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredMaintenanceMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredDefectMasterLookupController;
    @FXML private DbSearchLookupFieldController learningActivityLookupController;
    @FXML private DbSearchLookupFieldController trainingNeedAnalysisLookupController;
    @FXML private DbSearchLookupFieldController publicationMasterLookupController;
    @FXML private DbSearchLookupFieldController configurationLookupController;
    @FXML private DbSearchLookupFieldController configurationMasterLookupController;
    @FXML private ComboBox<String> serialTypeComboBox;
    @FXML private ComboBox<String> serialNumberTypeComboBox;
    @FXML private TextField serialPrefixField;
    @FXML private TextField serialFromField;
    @FXML private TextField serialToField;
    @FXML private ComboBox<String> rangeShiftComboBox;
    @FXML private Node partMasterLookupGroup;
    @FXML private Node partBomLookupGroup;
    @FXML private Node rotableLookupGroup;
    @FXML private Node rotableSelectionModeGroup;
    @FXML private CheckBox chooseRotableCheckBox;
    @FXML private CheckBox choosePartSerialCheckBox;
    @FXML private Node serialTypeGroup;
    @FXML private Node serialPrefixGroup;
    @FXML private Node serialFromGroup;
    @FXML private Node serialToGroup;
    @FXML private Node serialNumberGroup;
    @FXML private Node rangeRemarksGroup;
    @FXML private DbSearchLookupFieldController partMasterLookupController;
    @FXML private DbSearchLookupFieldController partBomLookupController;
    @FXML private DbSearchLookupFieldController rotableLookupController;
    @FXML private DbSearchLookupFieldController operationSpecificationLookupController;
    @FXML private TextField rangeRemarksField;
    @FXML private TextField serialNumberField;
    @FXML private CheckBox appliesToFlightOperationsCheckBox;
    @FXML private CheckBox appliesToGroundOperationsCheckBox;
    @FXML private CheckBox appliesToLineMaintenanceCheckBox;
    @FXML private CheckBox appliesToBaseMaintenanceCheckBox;
    @FXML private CheckBox appliesToComponentMaintenanceCheckBox;
    @FXML private TextField startDateField;
    @FXML private TextField endDateField;

    public EffectivitySectionController(EffectivityViewModel viewModel) {
        this.viewModel = viewModel;
    }

    public void setTypeDriven(boolean typeDriven) {
        this.typeDriven = typeDriven;
    }

    @Override public void disposeView() {
        if (bound) unbindCurrentViewModel();
        bound = false;
    }

    @FXML
    private void initialize() {
        if (mainTabButton != null) {
            configureTabs();
        }
        configureRotableSelectionMode();
    }

    public void bindTo(EffectivityViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        if (bound && viewModel == this.viewModel) {
            return;
        }
        if (bound) unbindCurrentViewModel();
        this.viewModel = viewModel;
        bound = true;

        setItems(scopeComboBox, viewModel.scopes());
        setItems(relationshipTypeComboBox, viewModel.relationshipTypes());
        setItems(targetTypeComboBox, viewModel.targetTypes());
        if (typeDriven && targetTypeComboBox != null) {
            setItems(targetTypeComboBox, EffectivityEditorTypes.TYPES);
            targetTypeComboBox.setConverter(new StringConverter<>() {
                @Override public String toString(String value) { return EffectivityEditorTypes.label(value); }
                @Override public String fromString(String label) {
                    return EffectivityEditorTypes.TYPES.stream()
                            .filter(type -> EffectivityEditorTypes.label(type).equals(label))
                            .findFirst().orElse(label);
                }
            });
        }
        setItems(nodeTypeComboBox, viewModel.nodeTypes());
        setItems(serialTypeComboBox, viewModel.serialTypes());
        setItems(serialNumberTypeComboBox, viewModel.serialNumberTypes());
        setItems(rangeShiftComboBox, viewModel.rangeShifts());

        bind(scopeComboBox, viewModel.scopeProperty());
        if (scopeComboBox != null) scopeComboBox.setDisable(true);
        bind(codeField, viewModel.codeProperty());
        bindLookup(deferredDefectMasterLookupController, "deferred-defects", "Deferred Defect",
                "Search deferred defect", viewModel.deferredDefectMasterIdProperty(), viewModel.deferredDefectLookupQueryProperty());
        bindLookup(learningActivityLookupController, "learningactivity", "Learning Activity",
                "Search learning activity", viewModel.learningActivityIdProperty(), viewModel.learningActivityLookupQueryProperty());
        bindLookup(trainingNeedAnalysisLookupController, "trainingneedanalysis", "Training Need Analysis",
                "Search training need analysis", viewModel.trainingNeedAnalysisIdProperty(), viewModel.trainingNeedAnalysisLookupQueryProperty());
        bind(titleField, viewModel.titleProperty());
        bindLookup(parentEffectivityLookupController, "effectivity", "Existing Effectivity", "Search existing effectivity",
                viewModel.parentEffectivityIdProperty(), viewModel.parentEffectivityLookupQueryProperty());
        parentEffectivityListener = (observable, oldValue, newValue) -> {
            if (newValue != null && !newValue.isBlank()) {
                viewModel.nodeTypeProperty().set("LinkedObject");
            }
        };
        viewModel.parentEffectivityIdProperty().addListener(parentEffectivityListener);
        if (!viewModel.parentEffectivityIdProperty().get().isBlank()) {
            viewModel.nodeTypeProperty().set("LinkedObject");
        }
        bind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        bind(targetTypeComboBox, viewModel.targetTypeProperty());
        if (itemTypeComboBox != null) {
            setItems(itemTypeComboBox, java.util.List.of("Rotable", "Aircraft", "Component", "Part", "Hardware", "Consumable",
                    "Expendable", "Repairable", "Tool", "RawMaterial", "OtherMaterial", "Software"));
            bind(itemTypeComboBox, viewModel.targetTypeProperty());
            itemTypeListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.targetTypeProperty().addListener(itemTypeListener);
            applyItemTypeVisibility();
        }
        bind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        bind(sequenceNumberField, viewModel.sequenceNumberProperty());
        bindLookup(maintenanceTaskLookupController, "maintenance-tasks", "Maintenance Task", "Search maintenance task",
                viewModel.maintenanceTaskIdProperty(), viewModel.maintenanceTaskLookupQueryProperty());
        bindLookup(taskCardLookupController, "task-cards", "Task Card", "Search task card",
                viewModel.taskCardIdProperty(), viewModel.taskCardLookupQueryProperty());
        bindLookup(requirementLookupController, "requirements", "Requirement", "Search requirement",
                viewModel.requirementIdProperty(), viewModel.requirementLookupQueryProperty());
        bindLookup(maintenanceProgramMasterLookupController, "maintenance-programs", "Maintenance Program",
                "Search maintenance program", viewModel.maintenanceProgramMasterIdProperty(),
                viewModel.maintenanceProgramMasterLookupQueryProperty());
        bindLookup(deferredMaintenanceMasterLookupController, "deferred-maintenance", "Deferred Maintenance",
                "Search deferred maintenance", viewModel.deferredMaintenanceMasterIdProperty(),
                viewModel.deferredMaintenanceMasterLookupQueryProperty());
        bindLookup(publicationMasterLookupController, "publications", "Publication", "Search publication",
                viewModel.publicationMasterIdProperty(), viewModel.publicationMasterLookupQueryProperty());
        bindLookup(configurationLookupController, "configurations", "Configuration", "Search configuration",
                viewModel.configurationIdProperty(), viewModel.configurationLookupQueryProperty());
        bindLookup(configurationMasterLookupController, "configuration-masters", "Configuration Master",
                "Search configuration master", viewModel.configurationMasterIdProperty(),
                viewModel.configurationMasterLookupQueryProperty());
        bind(serialTypeComboBox, viewModel.serialTypeProperty());
        if (itemTypeComboBox != null) {
            serialSelectionListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.serialTypeProperty().addListener(serialSelectionListener);
        }
        bind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        bind(serialPrefixField, viewModel.serialPrefixProperty());
        bind(serialFromField, viewModel.serialFromProperty());
        bind(serialToField, viewModel.serialToProperty());
        bind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        bindLookup(partMasterLookupController, "parts", "Part Master", "Search part master",
                viewModel.partMasterIdProperty(), viewModel.partMasterLookupQueryProperty());
        bindLookup(partBomLookupController, "part-boms", "Part BOM", "Search part BOM",
                viewModel.partBomIdProperty(), viewModel.partBomLookupQueryProperty());
        bindLookup(rotableLookupController, "rotables", "Rotable", "Search rotable",
                viewModel.rotableIdProperty(), viewModel.rotableLookupQueryProperty());
        bindLookup(operationSpecificationLookupController, "operation-specifications", "Operations",
                "Search operations", viewModel.operationSpecificationIdProperty(),
                viewModel.operationSpecificationLookupQueryProperty());
        bind(rangeRemarksField, viewModel.rangeRemarksProperty());
        bind(serialNumberField, viewModel.serialNumberProperty());
        bind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        bind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        bind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        bind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        bind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        bind(startDateField, viewModel.startDateProperty());
        bind(endDateField, viewModel.endDateProperty());
        scopeVisibilityListener = (observable, oldScope, newScope) -> applyVisibility();
        viewModel.scopeProperty().addListener(scopeVisibilityListener);
        selectRotableModeFromCurrentModel();
        applyVisibility();
        if (typeDriven) {
            editorTypeListener = (observable, oldType, newType) -> applyEditorType();
            viewModel.targetTypeProperty().addListener(editorTypeListener);
            applyEditorType();
        }
    }

    private void applyEditorType() {
        String type = viewModel.targetTypeProperty().get();
        if (targetTypeLabel != null) targetTypeLabel.setText("Effectivity Type");
        if (targetTypeComboBox != null && type != null && !type.isBlank()
                && !targetTypeComboBox.getItems().contains(type)) targetTypeComboBox.getItems().add(type);
        if (editorTypeHint != null) {
            setVisible(editorTypeHint, true);
            editorTypeHint.setText("main".equals(EffectivityEditorTypes.tabFor(type))
                    ? "Choose the subject of this effectivity. Its matching editor opens automatically. Business Partner restrictions are maintained in Links."
                    : EffectivityEditorTypes.label(type) + " Effectivity is ready in the matching tab. Validity, Remarks, Audit and Links remain shared. Switching types preserves entered values; review existing criteria before saving.");
        }
        if (editorSectionTitle != null) editorSectionTitle.setText(EffectivityEditorTypes.label(type) + " Effectivity");
        setVisible(rotableSelectionModeGroup, false);
    }

    @FXML private void onMainTabClicked() { showTab(mainTabContent); }
    @FXML private void onPartSerialTabClicked() { showTab(partSerialTabContent); }
    @FXML private void onOperatorTabClicked() { showTab(operatorTabContent); }
    @FXML private void onValidityTabClicked() { showTab(validityTabContent); }
    @FXML private void onRemarksTabClicked() { showTab(remarksTabContent); }
    @FXML private void onAuditTabClicked() { showTab(auditTabContent); }

    private void configureTabs() {
        ToggleGroup group = new ToggleGroup();
        for (ToggleButton button : new ToggleButton[]{mainTabButton, partSerialTabButton, operatorTabButton, validityTabButton, remarksTabButton, auditTabButton}) {
            if (button != null) {
                button.setToggleGroup(group);
            }
        }
        group.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
            if (newToggle == null && oldToggle != null) {
                oldToggle.setSelected(true);
            }
        });
        mainTabButton.setSelected(true);
        showTab(mainTabContent);
    }

    private void showTab(Node selectedContent) {
        for (Node content : new Node[]{mainTabContent, partSerialTabContent, operatorTabContent, validityTabContent, remarksTabContent, auditTabContent}) {
            if (content == null) {
                continue;
            }
            boolean selected = content == selectedContent;
            content.setManaged(selected);
            content.setVisible(selected);
        }
    }

    private void unbindCurrentViewModel() {
        if (editorTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(editorTypeListener);
            editorTypeListener = null;
        }
        if (serialSelectionListener != null) {
            viewModel.serialTypeProperty().removeListener(serialSelectionListener);
            serialSelectionListener = null;
        }
        if (itemTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(itemTypeListener);
            itemTypeListener = null;
        }
        unbind(itemTypeComboBox, viewModel.targetTypeProperty());
        if (scopeVisibilityListener != null) {
            viewModel.scopeProperty().removeListener(scopeVisibilityListener);
            scopeVisibilityListener = null;
        }
        if (parentEffectivityListener != null) {
            viewModel.parentEffectivityIdProperty().removeListener(parentEffectivityListener);
            parentEffectivityListener = null;
        }
        unbind(scopeComboBox, viewModel.scopeProperty());
        unbind(codeField, viewModel.codeProperty());
        unbind(titleField, viewModel.titleProperty());
        unbind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        unbind(targetTypeComboBox, viewModel.targetTypeProperty());
        unbind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        unbind(sequenceNumberField, viewModel.sequenceNumberProperty());
        unbind(serialTypeComboBox, viewModel.serialTypeProperty());
        unbind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        unbind(serialPrefixField, viewModel.serialPrefixProperty());
        unbind(serialFromField, viewModel.serialFromProperty());
        unbind(serialToField, viewModel.serialToProperty());
        unbind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        unbind(rangeRemarksField, viewModel.rangeRemarksProperty());
        unbind(serialNumberField, viewModel.serialNumberProperty());
        unbind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        unbind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        unbind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        unbind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        unbind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        unbind(startDateField, viewModel.startDateProperty());
        unbind(endDateField, viewModel.endDateProperty());
    }

    private void applyVisibility() {
        for (Node node : new Node[]{
                parentEffectivityLookupGroup, maintenanceTaskLookupGroup, taskCardLookupGroup,
                requirementLookupGroup, maintenanceProgramLookupGroup, deferredMaintenanceLookupGroup,
                publicationLookupGroup, configurationLookupGroup, configurationMasterLookupGroup,
                partMasterLookupGroup, partBomLookupGroup, rotableLookupGroup,
                rotableSelectionModeGroup, serialTypeGroup, serialPrefixGroup, serialFromGroup, serialToGroup,
                serialNumberGroup, rangeRemarksGroup
        }) {
            setVisible(node, true);
        }
        applyRotableSelectionMode();
        applyItemTypeVisibility();
        if (typeDriven) applyEditorType();
    }

    private void applyItemTypeVisibility() {
        if (itemTypeComboBox == null) return;
        String type = viewModel.targetTypeProperty().get();
        boolean software = "Software".equals(type);
        boolean rotable = java.util.List.of("Rotable", "Aircraft", "Component").contains(type == null ? "" : type);
        boolean part = itemTypeComboBox.getItems().contains(type) && !software && !rotable;
        if (software && configurationLookupController != null) {
            configurationLookupController.configure("configurations", "Software Configuration",
                    "Search software configuration", java.util.List.of(
                            new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                                    "Functional Position Type", "E", "Software")));
        }
        if (part && partMasterLookupController != null) {
            String material = "OtherMaterial".equals(type) ? "Other" : type;
            boolean filter = !java.util.List.of("Part", "Hardware").contains(type);
            partMasterLookupController.configure("parts", "Part Master", "Search " + type,
                    filter ? java.util.List.of(new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                            "Material Class", "E", material)) : java.util.List.of());
        }
        setVisible(softwareLookupGroup, software);
        setVisible(rotableLookupGroup, rotable);
        setVisible(partMasterLookupGroup, part);
        boolean serials = part || rotable;
        String selection = viewModel.serialTypeProperty().get();
        boolean range = serials && "Range".equals(selection);
        setVisible(serialTypeGroup, serials);
        setVisible(serialNumberTypeGroup, serials);
        setVisible(serialNumberGroup, serials && ("Single".equals(selection) || "Pattern".equals(selection)));
        setVisible(serialPrefixGroup, serials);
        setVisible(serialFromGroup, range);
        setVisible(serialToGroup, range);
        setVisible(rangeShiftGroup, range);
        setVisible(rangeRemarksGroup, serials);
    }

    private void configureRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        chooseRotableCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                choosePartSerialCheckBox.setSelected(false);
            } else if (!choosePartSerialCheckBox.isSelected()) {
                chooseRotableCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        choosePartSerialCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                chooseRotableCheckBox.setSelected(false);
            } else if (!chooseRotableCheckBox.isSelected()) {
                choosePartSerialCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        chooseRotableCheckBox.setSelected(true);
        applyRotableSelectionMode();
    }

    private void selectRotableModeFromCurrentModel() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null || viewModel == null) {
            return;
        }
        boolean hasRotable = hasText(viewModel.rotableIdProperty().get()) || hasText(viewModel.rotableLookupQueryProperty().get());
        boolean hasPartSerial = hasText(viewModel.partMasterIdProperty().get())
                || hasText(viewModel.partMasterLookupQueryProperty().get())
                || hasText(viewModel.serialNumberProperty().get())
                || hasText(viewModel.serialPrefixProperty().get())
                || hasText(viewModel.serialFromProperty().get())
                || hasText(viewModel.serialToProperty().get())
                || hasText(viewModel.rangeRemarksProperty().get());
        choosePartSerialCheckBox.setSelected(!hasRotable && hasPartSerial);
        chooseRotableCheckBox.setSelected(hasRotable || !hasPartSerial);
    }

    private void applyRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        boolean chooseRotable = chooseRotableCheckBox.isSelected();
        setVisible(rotableLookupGroup, chooseRotable);
        setVisible(partMasterLookupGroup, !chooseRotable);
        setVisible(serialTypeGroup, !chooseRotable);
        setVisible(serialNumberGroup, !chooseRotable);
        setVisible(serialPrefixGroup, !chooseRotable);
        setVisible(serialFromGroup, !chooseRotable);
        setVisible(serialToGroup, !chooseRotable);
        setVisible(rangeRemarksGroup, !chooseRotable);
    }

    private boolean hasText(String value) {
        return value != null && !value.isBlank();
    }

    private void setVisible(Node node, boolean visible) {
        if (node == null) {
            return;
        }
        node.setVisible(visible);
        node.setManaged(visible);
    }

    private void bind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().bindBidirectional(property);
        }
    }

    private void unbind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().unbindBidirectional(property);
        }
    }

    private void bind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().bindBidirectional(property);
        }
    }

    private void unbind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().unbindBidirectional(property);
        }
    }

    private void bind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().bindBidirectional(property);
        }
    }

    private void unbind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().unbindBidirectional(property);
        }
    }

    private void setItems(ComboBox<String> control, java.util.List<String> items) {
        if (control != null) {
            control.getItems().setAll(items);
        }
    }

    private void bindLookup(
            DbSearchLookupFieldController controller,
            String definitionKey,
            String label,
            String prompt,
            StringProperty selectedId,
            StringProperty query
    ) {
        if (controller != null) {
            LookupFieldBinding.bind(controller, definitionKey, label, prompt, selectedId, query);
        }
    }
}

Effectivity / Publication

GUI review pending

Publication link.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Publication
Shared form reference · 2026-10-10T12:34:18.539891 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Publication
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (2)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Relationship Type
relationshipTypeComboBox
ComboBox
relationshipTypeProperty
Review requiredness, validation and display.
publication Master
publicationMasterLookup
UUID search lookup
publicationMasterIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Source evidence

Tab key: effectivity--publication
Section: Entire form
FXML SHA-256: c97e42f7233774073f834d8cb2f7a31da3e15bd24d9ae1f8f83cb574164e336e

coflyr-fe/src/main/resources/com/coflyr/fe/shared/effectivity/views/effectivity-publication-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      styleClass="standard-section-flat"
      VBox.vgrow="ALWAYS">
    <children>
        <Label fx:id="editorSectionTitle" styleClass="section-title" text="Publication"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Relationship Type"/>
                        <ComboBox fx:id="relationshipTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="publicationLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <fx:include fx:id="publicationMasterLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController
package com.coflyr.fe.shared.effectivity.controllers;

import com.coflyr.fe.shared.effectivity.viewmodels.EffectivityViewModel;
import com.coflyr.fe.shared.effectivity.EffectivityEditorTypes;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.beans.value.ChangeListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.util.StringConverter;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EffectivitySectionController implements com.coflyr.fe.config.DisposableView {
    private EffectivityViewModel viewModel;
    private boolean bound;
    private boolean typeDriven;
    private ChangeListener<String> editorTypeListener;
    @FXML private Label targetTypeLabel;
    @FXML private Label editorTypeHint;
    @FXML private Label editorSectionTitle;
    private ChangeListener<String> scopeVisibilityListener;
    private ChangeListener<String> parentEffectivityListener;
    private ChangeListener<String> itemTypeListener;
    private ChangeListener<String> serialSelectionListener;
    @FXML private Node serialNumberTypeGroup;
    @FXML private Node rangeShiftGroup;
    @FXML private ComboBox<String> itemTypeComboBox;
    @FXML private Node softwareLookupGroup;

    @FXML private ToggleButton mainTabButton;
    @FXML private ToggleButton partSerialTabButton;
    @FXML private ToggleButton operatorTabButton;
    @FXML private ToggleButton validityTabButton;
    @FXML private ToggleButton remarksTabButton;
    @FXML private ToggleButton auditTabButton;
    @FXML private Node mainTabContent;
    @FXML private Node partSerialTabContent;
    @FXML private Node operatorTabContent;
    @FXML private Node validityTabContent;
    @FXML private Node remarksTabContent;
    @FXML private Node auditTabContent;
    @FXML private ComboBox<String> scopeComboBox;
    @FXML private Node parentEffectivityLookupGroup;
    @FXML private Node maintenanceTaskLookupGroup;
    @FXML private Node taskCardLookupGroup;
    @FXML private Node requirementLookupGroup;
    @FXML private Node maintenanceProgramLookupGroup;
    @FXML private Node deferredMaintenanceLookupGroup;
    @FXML private Node publicationLookupGroup;
    @FXML private Node configurationLookupGroup;
    @FXML private Node configurationMasterLookupGroup;
    @FXML private DbSearchLookupFieldController parentEffectivityLookupController;
    @FXML private TextField codeField;
    @FXML private TextField titleField;
    @FXML private ComboBox<String> relationshipTypeComboBox;
    @FXML private ComboBox<String> targetTypeComboBox;
    @FXML private ComboBox<String> nodeTypeComboBox;
    @FXML private TextField sequenceNumberField;
    @FXML private DbSearchLookupFieldController maintenanceTaskLookupController;
    @FXML private DbSearchLookupFieldController taskCardLookupController;
    @FXML private DbSearchLookupFieldController requirementLookupController;
    @FXML private DbSearchLookupFieldController maintenanceProgramMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredMaintenanceMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredDefectMasterLookupController;
    @FXML private DbSearchLookupFieldController learningActivityLookupController;
    @FXML private DbSearchLookupFieldController trainingNeedAnalysisLookupController;
    @FXML private DbSearchLookupFieldController publicationMasterLookupController;
    @FXML private DbSearchLookupFieldController configurationLookupController;
    @FXML private DbSearchLookupFieldController configurationMasterLookupController;
    @FXML private ComboBox<String> serialTypeComboBox;
    @FXML private ComboBox<String> serialNumberTypeComboBox;
    @FXML private TextField serialPrefixField;
    @FXML private TextField serialFromField;
    @FXML private TextField serialToField;
    @FXML private ComboBox<String> rangeShiftComboBox;
    @FXML private Node partMasterLookupGroup;
    @FXML private Node partBomLookupGroup;
    @FXML private Node rotableLookupGroup;
    @FXML private Node rotableSelectionModeGroup;
    @FXML private CheckBox chooseRotableCheckBox;
    @FXML private CheckBox choosePartSerialCheckBox;
    @FXML private Node serialTypeGroup;
    @FXML private Node serialPrefixGroup;
    @FXML private Node serialFromGroup;
    @FXML private Node serialToGroup;
    @FXML private Node serialNumberGroup;
    @FXML private Node rangeRemarksGroup;
    @FXML private DbSearchLookupFieldController partMasterLookupController;
    @FXML private DbSearchLookupFieldController partBomLookupController;
    @FXML private DbSearchLookupFieldController rotableLookupController;
    @FXML private DbSearchLookupFieldController operationSpecificationLookupController;
    @FXML private TextField rangeRemarksField;
    @FXML private TextField serialNumberField;
    @FXML private CheckBox appliesToFlightOperationsCheckBox;
    @FXML private CheckBox appliesToGroundOperationsCheckBox;
    @FXML private CheckBox appliesToLineMaintenanceCheckBox;
    @FXML private CheckBox appliesToBaseMaintenanceCheckBox;
    @FXML private CheckBox appliesToComponentMaintenanceCheckBox;
    @FXML private TextField startDateField;
    @FXML private TextField endDateField;

    public EffectivitySectionController(EffectivityViewModel viewModel) {
        this.viewModel = viewModel;
    }

    public void setTypeDriven(boolean typeDriven) {
        this.typeDriven = typeDriven;
    }

    @Override public void disposeView() {
        if (bound) unbindCurrentViewModel();
        bound = false;
    }

    @FXML
    private void initialize() {
        if (mainTabButton != null) {
            configureTabs();
        }
        configureRotableSelectionMode();
    }

    public void bindTo(EffectivityViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        if (bound && viewModel == this.viewModel) {
            return;
        }
        if (bound) unbindCurrentViewModel();
        this.viewModel = viewModel;
        bound = true;

        setItems(scopeComboBox, viewModel.scopes());
        setItems(relationshipTypeComboBox, viewModel.relationshipTypes());
        setItems(targetTypeComboBox, viewModel.targetTypes());
        if (typeDriven && targetTypeComboBox != null) {
            setItems(targetTypeComboBox, EffectivityEditorTypes.TYPES);
            targetTypeComboBox.setConverter(new StringConverter<>() {
                @Override public String toString(String value) { return EffectivityEditorTypes.label(value); }
                @Override public String fromString(String label) {
                    return EffectivityEditorTypes.TYPES.stream()
                            .filter(type -> EffectivityEditorTypes.label(type).equals(label))
                            .findFirst().orElse(label);
                }
            });
        }
        setItems(nodeTypeComboBox, viewModel.nodeTypes());
        setItems(serialTypeComboBox, viewModel.serialTypes());
        setItems(serialNumberTypeComboBox, viewModel.serialNumberTypes());
        setItems(rangeShiftComboBox, viewModel.rangeShifts());

        bind(scopeComboBox, viewModel.scopeProperty());
        if (scopeComboBox != null) scopeComboBox.setDisable(true);
        bind(codeField, viewModel.codeProperty());
        bindLookup(deferredDefectMasterLookupController, "deferred-defects", "Deferred Defect",
                "Search deferred defect", viewModel.deferredDefectMasterIdProperty(), viewModel.deferredDefectLookupQueryProperty());
        bindLookup(learningActivityLookupController, "learningactivity", "Learning Activity",
                "Search learning activity", viewModel.learningActivityIdProperty(), viewModel.learningActivityLookupQueryProperty());
        bindLookup(trainingNeedAnalysisLookupController, "trainingneedanalysis", "Training Need Analysis",
                "Search training need analysis", viewModel.trainingNeedAnalysisIdProperty(), viewModel.trainingNeedAnalysisLookupQueryProperty());
        bind(titleField, viewModel.titleProperty());
        bindLookup(parentEffectivityLookupController, "effectivity", "Existing Effectivity", "Search existing effectivity",
                viewModel.parentEffectivityIdProperty(), viewModel.parentEffectivityLookupQueryProperty());
        parentEffectivityListener = (observable, oldValue, newValue) -> {
            if (newValue != null && !newValue.isBlank()) {
                viewModel.nodeTypeProperty().set("LinkedObject");
            }
        };
        viewModel.parentEffectivityIdProperty().addListener(parentEffectivityListener);
        if (!viewModel.parentEffectivityIdProperty().get().isBlank()) {
            viewModel.nodeTypeProperty().set("LinkedObject");
        }
        bind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        bind(targetTypeComboBox, viewModel.targetTypeProperty());
        if (itemTypeComboBox != null) {
            setItems(itemTypeComboBox, java.util.List.of("Rotable", "Aircraft", "Component", "Part", "Hardware", "Consumable",
                    "Expendable", "Repairable", "Tool", "RawMaterial", "OtherMaterial", "Software"));
            bind(itemTypeComboBox, viewModel.targetTypeProperty());
            itemTypeListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.targetTypeProperty().addListener(itemTypeListener);
            applyItemTypeVisibility();
        }
        bind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        bind(sequenceNumberField, viewModel.sequenceNumberProperty());
        bindLookup(maintenanceTaskLookupController, "maintenance-tasks", "Maintenance Task", "Search maintenance task",
                viewModel.maintenanceTaskIdProperty(), viewModel.maintenanceTaskLookupQueryProperty());
        bindLookup(taskCardLookupController, "task-cards", "Task Card", "Search task card",
                viewModel.taskCardIdProperty(), viewModel.taskCardLookupQueryProperty());
        bindLookup(requirementLookupController, "requirements", "Requirement", "Search requirement",
                viewModel.requirementIdProperty(), viewModel.requirementLookupQueryProperty());
        bindLookup(maintenanceProgramMasterLookupController, "maintenance-programs", "Maintenance Program",
                "Search maintenance program", viewModel.maintenanceProgramMasterIdProperty(),
                viewModel.maintenanceProgramMasterLookupQueryProperty());
        bindLookup(deferredMaintenanceMasterLookupController, "deferred-maintenance", "Deferred Maintenance",
                "Search deferred maintenance", viewModel.deferredMaintenanceMasterIdProperty(),
                viewModel.deferredMaintenanceMasterLookupQueryProperty());
        bindLookup(publicationMasterLookupController, "publications", "Publication", "Search publication",
                viewModel.publicationMasterIdProperty(), viewModel.publicationMasterLookupQueryProperty());
        bindLookup(configurationLookupController, "configurations", "Configuration", "Search configuration",
                viewModel.configurationIdProperty(), viewModel.configurationLookupQueryProperty());
        bindLookup(configurationMasterLookupController, "configuration-masters", "Configuration Master",
                "Search configuration master", viewModel.configurationMasterIdProperty(),
                viewModel.configurationMasterLookupQueryProperty());
        bind(serialTypeComboBox, viewModel.serialTypeProperty());
        if (itemTypeComboBox != null) {
            serialSelectionListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.serialTypeProperty().addListener(serialSelectionListener);
        }
        bind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        bind(serialPrefixField, viewModel.serialPrefixProperty());
        bind(serialFromField, viewModel.serialFromProperty());
        bind(serialToField, viewModel.serialToProperty());
        bind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        bindLookup(partMasterLookupController, "parts", "Part Master", "Search part master",
                viewModel.partMasterIdProperty(), viewModel.partMasterLookupQueryProperty());
        bindLookup(partBomLookupController, "part-boms", "Part BOM", "Search part BOM",
                viewModel.partBomIdProperty(), viewModel.partBomLookupQueryProperty());
        bindLookup(rotableLookupController, "rotables", "Rotable", "Search rotable",
                viewModel.rotableIdProperty(), viewModel.rotableLookupQueryProperty());
        bindLookup(operationSpecificationLookupController, "operation-specifications", "Operations",
                "Search operations", viewModel.operationSpecificationIdProperty(),
                viewModel.operationSpecificationLookupQueryProperty());
        bind(rangeRemarksField, viewModel.rangeRemarksProperty());
        bind(serialNumberField, viewModel.serialNumberProperty());
        bind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        bind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        bind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        bind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        bind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        bind(startDateField, viewModel.startDateProperty());
        bind(endDateField, viewModel.endDateProperty());
        scopeVisibilityListener = (observable, oldScope, newScope) -> applyVisibility();
        viewModel.scopeProperty().addListener(scopeVisibilityListener);
        selectRotableModeFromCurrentModel();
        applyVisibility();
        if (typeDriven) {
            editorTypeListener = (observable, oldType, newType) -> applyEditorType();
            viewModel.targetTypeProperty().addListener(editorTypeListener);
            applyEditorType();
        }
    }

    private void applyEditorType() {
        String type = viewModel.targetTypeProperty().get();
        if (targetTypeLabel != null) targetTypeLabel.setText("Effectivity Type");
        if (targetTypeComboBox != null && type != null && !type.isBlank()
                && !targetTypeComboBox.getItems().contains(type)) targetTypeComboBox.getItems().add(type);
        if (editorTypeHint != null) {
            setVisible(editorTypeHint, true);
            editorTypeHint.setText("main".equals(EffectivityEditorTypes.tabFor(type))
                    ? "Choose the subject of this effectivity. Its matching editor opens automatically. Business Partner restrictions are maintained in Links."
                    : EffectivityEditorTypes.label(type) + " Effectivity is ready in the matching tab. Validity, Remarks, Audit and Links remain shared. Switching types preserves entered values; review existing criteria before saving.");
        }
        if (editorSectionTitle != null) editorSectionTitle.setText(EffectivityEditorTypes.label(type) + " Effectivity");
        setVisible(rotableSelectionModeGroup, false);
    }

    @FXML private void onMainTabClicked() { showTab(mainTabContent); }
    @FXML private void onPartSerialTabClicked() { showTab(partSerialTabContent); }
    @FXML private void onOperatorTabClicked() { showTab(operatorTabContent); }
    @FXML private void onValidityTabClicked() { showTab(validityTabContent); }
    @FXML private void onRemarksTabClicked() { showTab(remarksTabContent); }
    @FXML private void onAuditTabClicked() { showTab(auditTabContent); }

    private void configureTabs() {
        ToggleGroup group = new ToggleGroup();
        for (ToggleButton button : new ToggleButton[]{mainTabButton, partSerialTabButton, operatorTabButton, validityTabButton, remarksTabButton, auditTabButton}) {
            if (button != null) {
                button.setToggleGroup(group);
            }
        }
        group.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
            if (newToggle == null && oldToggle != null) {
                oldToggle.setSelected(true);
            }
        });
        mainTabButton.setSelected(true);
        showTab(mainTabContent);
    }

    private void showTab(Node selectedContent) {
        for (Node content : new Node[]{mainTabContent, partSerialTabContent, operatorTabContent, validityTabContent, remarksTabContent, auditTabContent}) {
            if (content == null) {
                continue;
            }
            boolean selected = content == selectedContent;
            content.setManaged(selected);
            content.setVisible(selected);
        }
    }

    private void unbindCurrentViewModel() {
        if (editorTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(editorTypeListener);
            editorTypeListener = null;
        }
        if (serialSelectionListener != null) {
            viewModel.serialTypeProperty().removeListener(serialSelectionListener);
            serialSelectionListener = null;
        }
        if (itemTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(itemTypeListener);
            itemTypeListener = null;
        }
        unbind(itemTypeComboBox, viewModel.targetTypeProperty());
        if (scopeVisibilityListener != null) {
            viewModel.scopeProperty().removeListener(scopeVisibilityListener);
            scopeVisibilityListener = null;
        }
        if (parentEffectivityListener != null) {
            viewModel.parentEffectivityIdProperty().removeListener(parentEffectivityListener);
            parentEffectivityListener = null;
        }
        unbind(scopeComboBox, viewModel.scopeProperty());
        unbind(codeField, viewModel.codeProperty());
        unbind(titleField, viewModel.titleProperty());
        unbind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        unbind(targetTypeComboBox, viewModel.targetTypeProperty());
        unbind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        unbind(sequenceNumberField, viewModel.sequenceNumberProperty());
        unbind(serialTypeComboBox, viewModel.serialTypeProperty());
        unbind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        unbind(serialPrefixField, viewModel.serialPrefixProperty());
        unbind(serialFromField, viewModel.serialFromProperty());
        unbind(serialToField, viewModel.serialToProperty());
        unbind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        unbind(rangeRemarksField, viewModel.rangeRemarksProperty());
        unbind(serialNumberField, viewModel.serialNumberProperty());
        unbind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        unbind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        unbind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        unbind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        unbind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        unbind(startDateField, viewModel.startDateProperty());
        unbind(endDateField, viewModel.endDateProperty());
    }

    private void applyVisibility() {
        for (Node node : new Node[]{
                parentEffectivityLookupGroup, maintenanceTaskLookupGroup, taskCardLookupGroup,
                requirementLookupGroup, maintenanceProgramLookupGroup, deferredMaintenanceLookupGroup,
                publicationLookupGroup, configurationLookupGroup, configurationMasterLookupGroup,
                partMasterLookupGroup, partBomLookupGroup, rotableLookupGroup,
                rotableSelectionModeGroup, serialTypeGroup, serialPrefixGroup, serialFromGroup, serialToGroup,
                serialNumberGroup, rangeRemarksGroup
        }) {
            setVisible(node, true);
        }
        applyRotableSelectionMode();
        applyItemTypeVisibility();
        if (typeDriven) applyEditorType();
    }

    private void applyItemTypeVisibility() {
        if (itemTypeComboBox == null) return;
        String type = viewModel.targetTypeProperty().get();
        boolean software = "Software".equals(type);
        boolean rotable = java.util.List.of("Rotable", "Aircraft", "Component").contains(type == null ? "" : type);
        boolean part = itemTypeComboBox.getItems().contains(type) && !software && !rotable;
        if (software && configurationLookupController != null) {
            configurationLookupController.configure("configurations", "Software Configuration",
                    "Search software configuration", java.util.List.of(
                            new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                                    "Functional Position Type", "E", "Software")));
        }
        if (part && partMasterLookupController != null) {
            String material = "OtherMaterial".equals(type) ? "Other" : type;
            boolean filter = !java.util.List.of("Part", "Hardware").contains(type);
            partMasterLookupController.configure("parts", "Part Master", "Search " + type,
                    filter ? java.util.List.of(new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                            "Material Class", "E", material)) : java.util.List.of());
        }
        setVisible(softwareLookupGroup, software);
        setVisible(rotableLookupGroup, rotable);
        setVisible(partMasterLookupGroup, part);
        boolean serials = part || rotable;
        String selection = viewModel.serialTypeProperty().get();
        boolean range = serials && "Range".equals(selection);
        setVisible(serialTypeGroup, serials);
        setVisible(serialNumberTypeGroup, serials);
        setVisible(serialNumberGroup, serials && ("Single".equals(selection) || "Pattern".equals(selection)));
        setVisible(serialPrefixGroup, serials);
        setVisible(serialFromGroup, range);
        setVisible(serialToGroup, range);
        setVisible(rangeShiftGroup, range);
        setVisible(rangeRemarksGroup, serials);
    }

    private void configureRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        chooseRotableCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                choosePartSerialCheckBox.setSelected(false);
            } else if (!choosePartSerialCheckBox.isSelected()) {
                chooseRotableCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        choosePartSerialCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                chooseRotableCheckBox.setSelected(false);
            } else if (!chooseRotableCheckBox.isSelected()) {
                choosePartSerialCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        chooseRotableCheckBox.setSelected(true);
        applyRotableSelectionMode();
    }

    private void selectRotableModeFromCurrentModel() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null || viewModel == null) {
            return;
        }
        boolean hasRotable = hasText(viewModel.rotableIdProperty().get()) || hasText(viewModel.rotableLookupQueryProperty().get());
        boolean hasPartSerial = hasText(viewModel.partMasterIdProperty().get())
                || hasText(viewModel.partMasterLookupQueryProperty().get())
                || hasText(viewModel.serialNumberProperty().get())
                || hasText(viewModel.serialPrefixProperty().get())
                || hasText(viewModel.serialFromProperty().get())
                || hasText(viewModel.serialToProperty().get())
                || hasText(viewModel.rangeRemarksProperty().get());
        choosePartSerialCheckBox.setSelected(!hasRotable && hasPartSerial);
        chooseRotableCheckBox.setSelected(hasRotable || !hasPartSerial);
    }

    private void applyRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        boolean chooseRotable = chooseRotableCheckBox.isSelected();
        setVisible(rotableLookupGroup, chooseRotable);
        setVisible(partMasterLookupGroup, !chooseRotable);
        setVisible(serialTypeGroup, !chooseRotable);
        setVisible(serialNumberGroup, !chooseRotable);
        setVisible(serialPrefixGroup, !chooseRotable);
        setVisible(serialFromGroup, !chooseRotable);
        setVisible(serialToGroup, !chooseRotable);
        setVisible(rangeRemarksGroup, !chooseRotable);
    }

    private boolean hasText(String value) {
        return value != null && !value.isBlank();
    }

    private void setVisible(Node node, boolean visible) {
        if (node == null) {
            return;
        }
        node.setVisible(visible);
        node.setManaged(visible);
    }

    private void bind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().bindBidirectional(property);
        }
    }

    private void unbind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().unbindBidirectional(property);
        }
    }

    private void bind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().bindBidirectional(property);
        }
    }

    private void unbind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().unbindBidirectional(property);
        }
    }

    private void bind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().bindBidirectional(property);
        }
    }

    private void unbind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().unbindBidirectional(property);
        }
    }

    private void setItems(ComboBox<String> control, java.util.List<String> items) {
        if (control != null) {
            control.getItems().setAll(items);
        }
    }

    private void bindLookup(
            DbSearchLookupFieldController controller,
            String definitionKey,
            String label,
            String prompt,
            StringProperty selectedId,
            StringProperty query
    ) {
        if (controller != null) {
            LookupFieldBinding.bind(controller, definitionKey, label, prompt, selectedId, query);
        }
    }
}

Effectivity / Configuration

GUI review pending

Configuration master and configuration links.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Configuration
Shared form reference · 2026-10-10T12:32:45.260022 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Configuration
Static JavaFX layout · 1280 × 320 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (3)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Relationship Type
relationshipTypeComboBox
ComboBox
relationshipTypeProperty
Review requiredness, validation and display.
configuration Master
configurationMasterLookup
UUID search lookup
configurationMasterIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
configuration
configurationLookup
UUID search lookup
configurationIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Source evidence

Tab key: effectivity--configuration
Section: Entire form
FXML SHA-256: af156f1cb339fd610286b78e16a4b7308fd711fcc1c2b122e5c6be65abc480c3

coflyr-fe/src/main/resources/com/coflyr/fe/shared/effectivity/views/effectivity-configuration-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      styleClass="standard-section-flat"
      VBox.vgrow="ALWAYS">
    <children>
        <Label fx:id="editorSectionTitle" styleClass="section-title" text="Configuration"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Relationship Type"/>
                        <ComboBox fx:id="relationshipTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="configurationMasterLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <fx:include fx:id="configurationMasterLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
                <VBox fx:id="configurationLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2">
                    <children>
                        <fx:include fx:id="configurationLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController
package com.coflyr.fe.shared.effectivity.controllers;

import com.coflyr.fe.shared.effectivity.viewmodels.EffectivityViewModel;
import com.coflyr.fe.shared.effectivity.EffectivityEditorTypes;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.beans.value.ChangeListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.util.StringConverter;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EffectivitySectionController implements com.coflyr.fe.config.DisposableView {
    private EffectivityViewModel viewModel;
    private boolean bound;
    private boolean typeDriven;
    private ChangeListener<String> editorTypeListener;
    @FXML private Label targetTypeLabel;
    @FXML private Label editorTypeHint;
    @FXML private Label editorSectionTitle;
    private ChangeListener<String> scopeVisibilityListener;
    private ChangeListener<String> parentEffectivityListener;
    private ChangeListener<String> itemTypeListener;
    private ChangeListener<String> serialSelectionListener;
    @FXML private Node serialNumberTypeGroup;
    @FXML private Node rangeShiftGroup;
    @FXML private ComboBox<String> itemTypeComboBox;
    @FXML private Node softwareLookupGroup;

    @FXML private ToggleButton mainTabButton;
    @FXML private ToggleButton partSerialTabButton;
    @FXML private ToggleButton operatorTabButton;
    @FXML private ToggleButton validityTabButton;
    @FXML private ToggleButton remarksTabButton;
    @FXML private ToggleButton auditTabButton;
    @FXML private Node mainTabContent;
    @FXML private Node partSerialTabContent;
    @FXML private Node operatorTabContent;
    @FXML private Node validityTabContent;
    @FXML private Node remarksTabContent;
    @FXML private Node auditTabContent;
    @FXML private ComboBox<String> scopeComboBox;
    @FXML private Node parentEffectivityLookupGroup;
    @FXML private Node maintenanceTaskLookupGroup;
    @FXML private Node taskCardLookupGroup;
    @FXML private Node requirementLookupGroup;
    @FXML private Node maintenanceProgramLookupGroup;
    @FXML private Node deferredMaintenanceLookupGroup;
    @FXML private Node publicationLookupGroup;
    @FXML private Node configurationLookupGroup;
    @FXML private Node configurationMasterLookupGroup;
    @FXML private DbSearchLookupFieldController parentEffectivityLookupController;
    @FXML private TextField codeField;
    @FXML private TextField titleField;
    @FXML private ComboBox<String> relationshipTypeComboBox;
    @FXML private ComboBox<String> targetTypeComboBox;
    @FXML private ComboBox<String> nodeTypeComboBox;
    @FXML private TextField sequenceNumberField;
    @FXML private DbSearchLookupFieldController maintenanceTaskLookupController;
    @FXML private DbSearchLookupFieldController taskCardLookupController;
    @FXML private DbSearchLookupFieldController requirementLookupController;
    @FXML private DbSearchLookupFieldController maintenanceProgramMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredMaintenanceMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredDefectMasterLookupController;
    @FXML private DbSearchLookupFieldController learningActivityLookupController;
    @FXML private DbSearchLookupFieldController trainingNeedAnalysisLookupController;
    @FXML private DbSearchLookupFieldController publicationMasterLookupController;
    @FXML private DbSearchLookupFieldController configurationLookupController;
    @FXML private DbSearchLookupFieldController configurationMasterLookupController;
    @FXML private ComboBox<String> serialTypeComboBox;
    @FXML private ComboBox<String> serialNumberTypeComboBox;
    @FXML private TextField serialPrefixField;
    @FXML private TextField serialFromField;
    @FXML private TextField serialToField;
    @FXML private ComboBox<String> rangeShiftComboBox;
    @FXML private Node partMasterLookupGroup;
    @FXML private Node partBomLookupGroup;
    @FXML private Node rotableLookupGroup;
    @FXML private Node rotableSelectionModeGroup;
    @FXML private CheckBox chooseRotableCheckBox;
    @FXML private CheckBox choosePartSerialCheckBox;
    @FXML private Node serialTypeGroup;
    @FXML private Node serialPrefixGroup;
    @FXML private Node serialFromGroup;
    @FXML private Node serialToGroup;
    @FXML private Node serialNumberGroup;
    @FXML private Node rangeRemarksGroup;
    @FXML private DbSearchLookupFieldController partMasterLookupController;
    @FXML private DbSearchLookupFieldController partBomLookupController;
    @FXML private DbSearchLookupFieldController rotableLookupController;
    @FXML private DbSearchLookupFieldController operationSpecificationLookupController;
    @FXML private TextField rangeRemarksField;
    @FXML private TextField serialNumberField;
    @FXML private CheckBox appliesToFlightOperationsCheckBox;
    @FXML private CheckBox appliesToGroundOperationsCheckBox;
    @FXML private CheckBox appliesToLineMaintenanceCheckBox;
    @FXML private CheckBox appliesToBaseMaintenanceCheckBox;
    @FXML private CheckBox appliesToComponentMaintenanceCheckBox;
    @FXML private TextField startDateField;
    @FXML private TextField endDateField;

    public EffectivitySectionController(EffectivityViewModel viewModel) {
        this.viewModel = viewModel;
    }

    public void setTypeDriven(boolean typeDriven) {
        this.typeDriven = typeDriven;
    }

    @Override public void disposeView() {
        if (bound) unbindCurrentViewModel();
        bound = false;
    }

    @FXML
    private void initialize() {
        if (mainTabButton != null) {
            configureTabs();
        }
        configureRotableSelectionMode();
    }

    public void bindTo(EffectivityViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        if (bound && viewModel == this.viewModel) {
            return;
        }
        if (bound) unbindCurrentViewModel();
        this.viewModel = viewModel;
        bound = true;

        setItems(scopeComboBox, viewModel.scopes());
        setItems(relationshipTypeComboBox, viewModel.relationshipTypes());
        setItems(targetTypeComboBox, viewModel.targetTypes());
        if (typeDriven && targetTypeComboBox != null) {
            setItems(targetTypeComboBox, EffectivityEditorTypes.TYPES);
            targetTypeComboBox.setConverter(new StringConverter<>() {
                @Override public String toString(String value) { return EffectivityEditorTypes.label(value); }
                @Override public String fromString(String label) {
                    return EffectivityEditorTypes.TYPES.stream()
                            .filter(type -> EffectivityEditorTypes.label(type).equals(label))
                            .findFirst().orElse(label);
                }
            });
        }
        setItems(nodeTypeComboBox, viewModel.nodeTypes());
        setItems(serialTypeComboBox, viewModel.serialTypes());
        setItems(serialNumberTypeComboBox, viewModel.serialNumberTypes());
        setItems(rangeShiftComboBox, viewModel.rangeShifts());

        bind(scopeComboBox, viewModel.scopeProperty());
        if (scopeComboBox != null) scopeComboBox.setDisable(true);
        bind(codeField, viewModel.codeProperty());
        bindLookup(deferredDefectMasterLookupController, "deferred-defects", "Deferred Defect",
                "Search deferred defect", viewModel.deferredDefectMasterIdProperty(), viewModel.deferredDefectLookupQueryProperty());
        bindLookup(learningActivityLookupController, "learningactivity", "Learning Activity",
                "Search learning activity", viewModel.learningActivityIdProperty(), viewModel.learningActivityLookupQueryProperty());
        bindLookup(trainingNeedAnalysisLookupController, "trainingneedanalysis", "Training Need Analysis",
                "Search training need analysis", viewModel.trainingNeedAnalysisIdProperty(), viewModel.trainingNeedAnalysisLookupQueryProperty());
        bind(titleField, viewModel.titleProperty());
        bindLookup(parentEffectivityLookupController, "effectivity", "Existing Effectivity", "Search existing effectivity",
                viewModel.parentEffectivityIdProperty(), viewModel.parentEffectivityLookupQueryProperty());
        parentEffectivityListener = (observable, oldValue, newValue) -> {
            if (newValue != null && !newValue.isBlank()) {
                viewModel.nodeTypeProperty().set("LinkedObject");
            }
        };
        viewModel.parentEffectivityIdProperty().addListener(parentEffectivityListener);
        if (!viewModel.parentEffectivityIdProperty().get().isBlank()) {
            viewModel.nodeTypeProperty().set("LinkedObject");
        }
        bind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        bind(targetTypeComboBox, viewModel.targetTypeProperty());
        if (itemTypeComboBox != null) {
            setItems(itemTypeComboBox, java.util.List.of("Rotable", "Aircraft", "Component", "Part", "Hardware", "Consumable",
                    "Expendable", "Repairable", "Tool", "RawMaterial", "OtherMaterial", "Software"));
            bind(itemTypeComboBox, viewModel.targetTypeProperty());
            itemTypeListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.targetTypeProperty().addListener(itemTypeListener);
            applyItemTypeVisibility();
        }
        bind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        bind(sequenceNumberField, viewModel.sequenceNumberProperty());
        bindLookup(maintenanceTaskLookupController, "maintenance-tasks", "Maintenance Task", "Search maintenance task",
                viewModel.maintenanceTaskIdProperty(), viewModel.maintenanceTaskLookupQueryProperty());
        bindLookup(taskCardLookupController, "task-cards", "Task Card", "Search task card",
                viewModel.taskCardIdProperty(), viewModel.taskCardLookupQueryProperty());
        bindLookup(requirementLookupController, "requirements", "Requirement", "Search requirement",
                viewModel.requirementIdProperty(), viewModel.requirementLookupQueryProperty());
        bindLookup(maintenanceProgramMasterLookupController, "maintenance-programs", "Maintenance Program",
                "Search maintenance program", viewModel.maintenanceProgramMasterIdProperty(),
                viewModel.maintenanceProgramMasterLookupQueryProperty());
        bindLookup(deferredMaintenanceMasterLookupController, "deferred-maintenance", "Deferred Maintenance",
                "Search deferred maintenance", viewModel.deferredMaintenanceMasterIdProperty(),
                viewModel.deferredMaintenanceMasterLookupQueryProperty());
        bindLookup(publicationMasterLookupController, "publications", "Publication", "Search publication",
                viewModel.publicationMasterIdProperty(), viewModel.publicationMasterLookupQueryProperty());
        bindLookup(configurationLookupController, "configurations", "Configuration", "Search configuration",
                viewModel.configurationIdProperty(), viewModel.configurationLookupQueryProperty());
        bindLookup(configurationMasterLookupController, "configuration-masters", "Configuration Master",
                "Search configuration master", viewModel.configurationMasterIdProperty(),
                viewModel.configurationMasterLookupQueryProperty());
        bind(serialTypeComboBox, viewModel.serialTypeProperty());
        if (itemTypeComboBox != null) {
            serialSelectionListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.serialTypeProperty().addListener(serialSelectionListener);
        }
        bind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        bind(serialPrefixField, viewModel.serialPrefixProperty());
        bind(serialFromField, viewModel.serialFromProperty());
        bind(serialToField, viewModel.serialToProperty());
        bind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        bindLookup(partMasterLookupController, "parts", "Part Master", "Search part master",
                viewModel.partMasterIdProperty(), viewModel.partMasterLookupQueryProperty());
        bindLookup(partBomLookupController, "part-boms", "Part BOM", "Search part BOM",
                viewModel.partBomIdProperty(), viewModel.partBomLookupQueryProperty());
        bindLookup(rotableLookupController, "rotables", "Rotable", "Search rotable",
                viewModel.rotableIdProperty(), viewModel.rotableLookupQueryProperty());
        bindLookup(operationSpecificationLookupController, "operation-specifications", "Operations",
                "Search operations", viewModel.operationSpecificationIdProperty(),
                viewModel.operationSpecificationLookupQueryProperty());
        bind(rangeRemarksField, viewModel.rangeRemarksProperty());
        bind(serialNumberField, viewModel.serialNumberProperty());
        bind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        bind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        bind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        bind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        bind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        bind(startDateField, viewModel.startDateProperty());
        bind(endDateField, viewModel.endDateProperty());
        scopeVisibilityListener = (observable, oldScope, newScope) -> applyVisibility();
        viewModel.scopeProperty().addListener(scopeVisibilityListener);
        selectRotableModeFromCurrentModel();
        applyVisibility();
        if (typeDriven) {
            editorTypeListener = (observable, oldType, newType) -> applyEditorType();
            viewModel.targetTypeProperty().addListener(editorTypeListener);
            applyEditorType();
        }
    }

    private void applyEditorType() {
        String type = viewModel.targetTypeProperty().get();
        if (targetTypeLabel != null) targetTypeLabel.setText("Effectivity Type");
        if (targetTypeComboBox != null && type != null && !type.isBlank()
                && !targetTypeComboBox.getItems().contains(type)) targetTypeComboBox.getItems().add(type);
        if (editorTypeHint != null) {
            setVisible(editorTypeHint, true);
            editorTypeHint.setText("main".equals(EffectivityEditorTypes.tabFor(type))
                    ? "Choose the subject of this effectivity. Its matching editor opens automatically. Business Partner restrictions are maintained in Links."
                    : EffectivityEditorTypes.label(type) + " Effectivity is ready in the matching tab. Validity, Remarks, Audit and Links remain shared. Switching types preserves entered values; review existing criteria before saving.");
        }
        if (editorSectionTitle != null) editorSectionTitle.setText(EffectivityEditorTypes.label(type) + " Effectivity");
        setVisible(rotableSelectionModeGroup, false);
    }

    @FXML private void onMainTabClicked() { showTab(mainTabContent); }
    @FXML private void onPartSerialTabClicked() { showTab(partSerialTabContent); }
    @FXML private void onOperatorTabClicked() { showTab(operatorTabContent); }
    @FXML private void onValidityTabClicked() { showTab(validityTabContent); }
    @FXML private void onRemarksTabClicked() { showTab(remarksTabContent); }
    @FXML private void onAuditTabClicked() { showTab(auditTabContent); }

    private void configureTabs() {
        ToggleGroup group = new ToggleGroup();
        for (ToggleButton button : new ToggleButton[]{mainTabButton, partSerialTabButton, operatorTabButton, validityTabButton, remarksTabButton, auditTabButton}) {
            if (button != null) {
                button.setToggleGroup(group);
            }
        }
        group.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
            if (newToggle == null && oldToggle != null) {
                oldToggle.setSelected(true);
            }
        });
        mainTabButton.setSelected(true);
        showTab(mainTabContent);
    }

    private void showTab(Node selectedContent) {
        for (Node content : new Node[]{mainTabContent, partSerialTabContent, operatorTabContent, validityTabContent, remarksTabContent, auditTabContent}) {
            if (content == null) {
                continue;
            }
            boolean selected = content == selectedContent;
            content.setManaged(selected);
            content.setVisible(selected);
        }
    }

    private void unbindCurrentViewModel() {
        if (editorTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(editorTypeListener);
            editorTypeListener = null;
        }
        if (serialSelectionListener != null) {
            viewModel.serialTypeProperty().removeListener(serialSelectionListener);
            serialSelectionListener = null;
        }
        if (itemTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(itemTypeListener);
            itemTypeListener = null;
        }
        unbind(itemTypeComboBox, viewModel.targetTypeProperty());
        if (scopeVisibilityListener != null) {
            viewModel.scopeProperty().removeListener(scopeVisibilityListener);
            scopeVisibilityListener = null;
        }
        if (parentEffectivityListener != null) {
            viewModel.parentEffectivityIdProperty().removeListener(parentEffectivityListener);
            parentEffectivityListener = null;
        }
        unbind(scopeComboBox, viewModel.scopeProperty());
        unbind(codeField, viewModel.codeProperty());
        unbind(titleField, viewModel.titleProperty());
        unbind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        unbind(targetTypeComboBox, viewModel.targetTypeProperty());
        unbind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        unbind(sequenceNumberField, viewModel.sequenceNumberProperty());
        unbind(serialTypeComboBox, viewModel.serialTypeProperty());
        unbind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        unbind(serialPrefixField, viewModel.serialPrefixProperty());
        unbind(serialFromField, viewModel.serialFromProperty());
        unbind(serialToField, viewModel.serialToProperty());
        unbind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        unbind(rangeRemarksField, viewModel.rangeRemarksProperty());
        unbind(serialNumberField, viewModel.serialNumberProperty());
        unbind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        unbind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        unbind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        unbind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        unbind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        unbind(startDateField, viewModel.startDateProperty());
        unbind(endDateField, viewModel.endDateProperty());
    }

    private void applyVisibility() {
        for (Node node : new Node[]{
                parentEffectivityLookupGroup, maintenanceTaskLookupGroup, taskCardLookupGroup,
                requirementLookupGroup, maintenanceProgramLookupGroup, deferredMaintenanceLookupGroup,
                publicationLookupGroup, configurationLookupGroup, configurationMasterLookupGroup,
                partMasterLookupGroup, partBomLookupGroup, rotableLookupGroup,
                rotableSelectionModeGroup, serialTypeGroup, serialPrefixGroup, serialFromGroup, serialToGroup,
                serialNumberGroup, rangeRemarksGroup
        }) {
            setVisible(node, true);
        }
        applyRotableSelectionMode();
        applyItemTypeVisibility();
        if (typeDriven) applyEditorType();
    }

    private void applyItemTypeVisibility() {
        if (itemTypeComboBox == null) return;
        String type = viewModel.targetTypeProperty().get();
        boolean software = "Software".equals(type);
        boolean rotable = java.util.List.of("Rotable", "Aircraft", "Component").contains(type == null ? "" : type);
        boolean part = itemTypeComboBox.getItems().contains(type) && !software && !rotable;
        if (software && configurationLookupController != null) {
            configurationLookupController.configure("configurations", "Software Configuration",
                    "Search software configuration", java.util.List.of(
                            new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                                    "Functional Position Type", "E", "Software")));
        }
        if (part && partMasterLookupController != null) {
            String material = "OtherMaterial".equals(type) ? "Other" : type;
            boolean filter = !java.util.List.of("Part", "Hardware").contains(type);
            partMasterLookupController.configure("parts", "Part Master", "Search " + type,
                    filter ? java.util.List.of(new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                            "Material Class", "E", material)) : java.util.List.of());
        }
        setVisible(softwareLookupGroup, software);
        setVisible(rotableLookupGroup, rotable);
        setVisible(partMasterLookupGroup, part);
        boolean serials = part || rotable;
        String selection = viewModel.serialTypeProperty().get();
        boolean range = serials && "Range".equals(selection);
        setVisible(serialTypeGroup, serials);
        setVisible(serialNumberTypeGroup, serials);
        setVisible(serialNumberGroup, serials && ("Single".equals(selection) || "Pattern".equals(selection)));
        setVisible(serialPrefixGroup, serials);
        setVisible(serialFromGroup, range);
        setVisible(serialToGroup, range);
        setVisible(rangeShiftGroup, range);
        setVisible(rangeRemarksGroup, serials);
    }

    private void configureRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        chooseRotableCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                choosePartSerialCheckBox.setSelected(false);
            } else if (!choosePartSerialCheckBox.isSelected()) {
                chooseRotableCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        choosePartSerialCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                chooseRotableCheckBox.setSelected(false);
            } else if (!chooseRotableCheckBox.isSelected()) {
                choosePartSerialCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        chooseRotableCheckBox.setSelected(true);
        applyRotableSelectionMode();
    }

    private void selectRotableModeFromCurrentModel() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null || viewModel == null) {
            return;
        }
        boolean hasRotable = hasText(viewModel.rotableIdProperty().get()) || hasText(viewModel.rotableLookupQueryProperty().get());
        boolean hasPartSerial = hasText(viewModel.partMasterIdProperty().get())
                || hasText(viewModel.partMasterLookupQueryProperty().get())
                || hasText(viewModel.serialNumberProperty().get())
                || hasText(viewModel.serialPrefixProperty().get())
                || hasText(viewModel.serialFromProperty().get())
                || hasText(viewModel.serialToProperty().get())
                || hasText(viewModel.rangeRemarksProperty().get());
        choosePartSerialCheckBox.setSelected(!hasRotable && hasPartSerial);
        chooseRotableCheckBox.setSelected(hasRotable || !hasPartSerial);
    }

    private void applyRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        boolean chooseRotable = chooseRotableCheckBox.isSelected();
        setVisible(rotableLookupGroup, chooseRotable);
        setVisible(partMasterLookupGroup, !chooseRotable);
        setVisible(serialTypeGroup, !chooseRotable);
        setVisible(serialNumberGroup, !chooseRotable);
        setVisible(serialPrefixGroup, !chooseRotable);
        setVisible(serialFromGroup, !chooseRotable);
        setVisible(serialToGroup, !chooseRotable);
        setVisible(rangeRemarksGroup, !chooseRotable);
    }

    private boolean hasText(String value) {
        return value != null && !value.isBlank();
    }

    private void setVisible(Node node, boolean visible) {
        if (node == null) {
            return;
        }
        node.setVisible(visible);
        node.setManaged(visible);
    }

    private void bind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().bindBidirectional(property);
        }
    }

    private void unbind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().unbindBidirectional(property);
        }
    }

    private void bind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().bindBidirectional(property);
        }
    }

    private void unbind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().unbindBidirectional(property);
        }
    }

    private void bind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().bindBidirectional(property);
        }
    }

    private void unbind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().unbindBidirectional(property);
        }
    }

    private void setItems(ComboBox<String> control, java.util.List<String> items) {
        if (control != null) {
            control.getItems().setAll(items);
        }
    }

    private void bindLookup(
            DbSearchLookupFieldController controller,
            String definitionKey,
            String label,
            String prompt,
            StringProperty selectedId,
            StringProperty query
    ) {
        if (controller != null) {
            LookupFieldBinding.bind(controller, definitionKey, label, prompt, selectedId, query);
        }
    }
}

Effectivity / Part

GUI review pending

Part master and BOM links.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Part
Shared form reference · 2026-10-10T12:33:31.270580 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Part
Static JavaFX layout · 1280 × 320 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (3)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Relationship Type
relationshipTypeComboBox
ComboBox
relationshipTypeProperty
Review requiredness, validation and display.
part Master
partMasterLookup
UUID search lookup
partMasterIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
part Bom
partBomLookup
UUID search lookup
partBomIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Source evidence

Tab key: effectivity--part
Section: Entire form
FXML SHA-256: b1128d47b0819fbf8def9cde8001f82a48aba46f271954ee12d434b4425ac60b

coflyr-fe/src/main/resources/com/coflyr/fe/shared/effectivity/views/effectivity-part-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      styleClass="standard-section-flat"
      VBox.vgrow="ALWAYS">
    <children>
        <Label fx:id="editorSectionTitle" styleClass="section-title" text="Part"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Relationship Type"/>
                        <ComboBox fx:id="relationshipTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="partMasterLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <fx:include fx:id="partMasterLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
                <VBox fx:id="partBomLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2">
                    <children>
                        <fx:include fx:id="partBomLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController
package com.coflyr.fe.shared.effectivity.controllers;

import com.coflyr.fe.shared.effectivity.viewmodels.EffectivityViewModel;
import com.coflyr.fe.shared.effectivity.EffectivityEditorTypes;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.beans.value.ChangeListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.util.StringConverter;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EffectivitySectionController implements com.coflyr.fe.config.DisposableView {
    private EffectivityViewModel viewModel;
    private boolean bound;
    private boolean typeDriven;
    private ChangeListener<String> editorTypeListener;
    @FXML private Label targetTypeLabel;
    @FXML private Label editorTypeHint;
    @FXML private Label editorSectionTitle;
    private ChangeListener<String> scopeVisibilityListener;
    private ChangeListener<String> parentEffectivityListener;
    private ChangeListener<String> itemTypeListener;
    private ChangeListener<String> serialSelectionListener;
    @FXML private Node serialNumberTypeGroup;
    @FXML private Node rangeShiftGroup;
    @FXML private ComboBox<String> itemTypeComboBox;
    @FXML private Node softwareLookupGroup;

    @FXML private ToggleButton mainTabButton;
    @FXML private ToggleButton partSerialTabButton;
    @FXML private ToggleButton operatorTabButton;
    @FXML private ToggleButton validityTabButton;
    @FXML private ToggleButton remarksTabButton;
    @FXML private ToggleButton auditTabButton;
    @FXML private Node mainTabContent;
    @FXML private Node partSerialTabContent;
    @FXML private Node operatorTabContent;
    @FXML private Node validityTabContent;
    @FXML private Node remarksTabContent;
    @FXML private Node auditTabContent;
    @FXML private ComboBox<String> scopeComboBox;
    @FXML private Node parentEffectivityLookupGroup;
    @FXML private Node maintenanceTaskLookupGroup;
    @FXML private Node taskCardLookupGroup;
    @FXML private Node requirementLookupGroup;
    @FXML private Node maintenanceProgramLookupGroup;
    @FXML private Node deferredMaintenanceLookupGroup;
    @FXML private Node publicationLookupGroup;
    @FXML private Node configurationLookupGroup;
    @FXML private Node configurationMasterLookupGroup;
    @FXML private DbSearchLookupFieldController parentEffectivityLookupController;
    @FXML private TextField codeField;
    @FXML private TextField titleField;
    @FXML private ComboBox<String> relationshipTypeComboBox;
    @FXML private ComboBox<String> targetTypeComboBox;
    @FXML private ComboBox<String> nodeTypeComboBox;
    @FXML private TextField sequenceNumberField;
    @FXML private DbSearchLookupFieldController maintenanceTaskLookupController;
    @FXML private DbSearchLookupFieldController taskCardLookupController;
    @FXML private DbSearchLookupFieldController requirementLookupController;
    @FXML private DbSearchLookupFieldController maintenanceProgramMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredMaintenanceMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredDefectMasterLookupController;
    @FXML private DbSearchLookupFieldController learningActivityLookupController;
    @FXML private DbSearchLookupFieldController trainingNeedAnalysisLookupController;
    @FXML private DbSearchLookupFieldController publicationMasterLookupController;
    @FXML private DbSearchLookupFieldController configurationLookupController;
    @FXML private DbSearchLookupFieldController configurationMasterLookupController;
    @FXML private ComboBox<String> serialTypeComboBox;
    @FXML private ComboBox<String> serialNumberTypeComboBox;
    @FXML private TextField serialPrefixField;
    @FXML private TextField serialFromField;
    @FXML private TextField serialToField;
    @FXML private ComboBox<String> rangeShiftComboBox;
    @FXML private Node partMasterLookupGroup;
    @FXML private Node partBomLookupGroup;
    @FXML private Node rotableLookupGroup;
    @FXML private Node rotableSelectionModeGroup;
    @FXML private CheckBox chooseRotableCheckBox;
    @FXML private CheckBox choosePartSerialCheckBox;
    @FXML private Node serialTypeGroup;
    @FXML private Node serialPrefixGroup;
    @FXML private Node serialFromGroup;
    @FXML private Node serialToGroup;
    @FXML private Node serialNumberGroup;
    @FXML private Node rangeRemarksGroup;
    @FXML private DbSearchLookupFieldController partMasterLookupController;
    @FXML private DbSearchLookupFieldController partBomLookupController;
    @FXML private DbSearchLookupFieldController rotableLookupController;
    @FXML private DbSearchLookupFieldController operationSpecificationLookupController;
    @FXML private TextField rangeRemarksField;
    @FXML private TextField serialNumberField;
    @FXML private CheckBox appliesToFlightOperationsCheckBox;
    @FXML private CheckBox appliesToGroundOperationsCheckBox;
    @FXML private CheckBox appliesToLineMaintenanceCheckBox;
    @FXML private CheckBox appliesToBaseMaintenanceCheckBox;
    @FXML private CheckBox appliesToComponentMaintenanceCheckBox;
    @FXML private TextField startDateField;
    @FXML private TextField endDateField;

    public EffectivitySectionController(EffectivityViewModel viewModel) {
        this.viewModel = viewModel;
    }

    public void setTypeDriven(boolean typeDriven) {
        this.typeDriven = typeDriven;
    }

    @Override public void disposeView() {
        if (bound) unbindCurrentViewModel();
        bound = false;
    }

    @FXML
    private void initialize() {
        if (mainTabButton != null) {
            configureTabs();
        }
        configureRotableSelectionMode();
    }

    public void bindTo(EffectivityViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        if (bound && viewModel == this.viewModel) {
            return;
        }
        if (bound) unbindCurrentViewModel();
        this.viewModel = viewModel;
        bound = true;

        setItems(scopeComboBox, viewModel.scopes());
        setItems(relationshipTypeComboBox, viewModel.relationshipTypes());
        setItems(targetTypeComboBox, viewModel.targetTypes());
        if (typeDriven && targetTypeComboBox != null) {
            setItems(targetTypeComboBox, EffectivityEditorTypes.TYPES);
            targetTypeComboBox.setConverter(new StringConverter<>() {
                @Override public String toString(String value) { return EffectivityEditorTypes.label(value); }
                @Override public String fromString(String label) {
                    return EffectivityEditorTypes.TYPES.stream()
                            .filter(type -> EffectivityEditorTypes.label(type).equals(label))
                            .findFirst().orElse(label);
                }
            });
        }
        setItems(nodeTypeComboBox, viewModel.nodeTypes());
        setItems(serialTypeComboBox, viewModel.serialTypes());
        setItems(serialNumberTypeComboBox, viewModel.serialNumberTypes());
        setItems(rangeShiftComboBox, viewModel.rangeShifts());

        bind(scopeComboBox, viewModel.scopeProperty());
        if (scopeComboBox != null) scopeComboBox.setDisable(true);
        bind(codeField, viewModel.codeProperty());
        bindLookup(deferredDefectMasterLookupController, "deferred-defects", "Deferred Defect",
                "Search deferred defect", viewModel.deferredDefectMasterIdProperty(), viewModel.deferredDefectLookupQueryProperty());
        bindLookup(learningActivityLookupController, "learningactivity", "Learning Activity",
                "Search learning activity", viewModel.learningActivityIdProperty(), viewModel.learningActivityLookupQueryProperty());
        bindLookup(trainingNeedAnalysisLookupController, "trainingneedanalysis", "Training Need Analysis",
                "Search training need analysis", viewModel.trainingNeedAnalysisIdProperty(), viewModel.trainingNeedAnalysisLookupQueryProperty());
        bind(titleField, viewModel.titleProperty());
        bindLookup(parentEffectivityLookupController, "effectivity", "Existing Effectivity", "Search existing effectivity",
                viewModel.parentEffectivityIdProperty(), viewModel.parentEffectivityLookupQueryProperty());
        parentEffectivityListener = (observable, oldValue, newValue) -> {
            if (newValue != null && !newValue.isBlank()) {
                viewModel.nodeTypeProperty().set("LinkedObject");
            }
        };
        viewModel.parentEffectivityIdProperty().addListener(parentEffectivityListener);
        if (!viewModel.parentEffectivityIdProperty().get().isBlank()) {
            viewModel.nodeTypeProperty().set("LinkedObject");
        }
        bind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        bind(targetTypeComboBox, viewModel.targetTypeProperty());
        if (itemTypeComboBox != null) {
            setItems(itemTypeComboBox, java.util.List.of("Rotable", "Aircraft", "Component", "Part", "Hardware", "Consumable",
                    "Expendable", "Repairable", "Tool", "RawMaterial", "OtherMaterial", "Software"));
            bind(itemTypeComboBox, viewModel.targetTypeProperty());
            itemTypeListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.targetTypeProperty().addListener(itemTypeListener);
            applyItemTypeVisibility();
        }
        bind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        bind(sequenceNumberField, viewModel.sequenceNumberProperty());
        bindLookup(maintenanceTaskLookupController, "maintenance-tasks", "Maintenance Task", "Search maintenance task",
                viewModel.maintenanceTaskIdProperty(), viewModel.maintenanceTaskLookupQueryProperty());
        bindLookup(taskCardLookupController, "task-cards", "Task Card", "Search task card",
                viewModel.taskCardIdProperty(), viewModel.taskCardLookupQueryProperty());
        bindLookup(requirementLookupController, "requirements", "Requirement", "Search requirement",
                viewModel.requirementIdProperty(), viewModel.requirementLookupQueryProperty());
        bindLookup(maintenanceProgramMasterLookupController, "maintenance-programs", "Maintenance Program",
                "Search maintenance program", viewModel.maintenanceProgramMasterIdProperty(),
                viewModel.maintenanceProgramMasterLookupQueryProperty());
        bindLookup(deferredMaintenanceMasterLookupController, "deferred-maintenance", "Deferred Maintenance",
                "Search deferred maintenance", viewModel.deferredMaintenanceMasterIdProperty(),
                viewModel.deferredMaintenanceMasterLookupQueryProperty());
        bindLookup(publicationMasterLookupController, "publications", "Publication", "Search publication",
                viewModel.publicationMasterIdProperty(), viewModel.publicationMasterLookupQueryProperty());
        bindLookup(configurationLookupController, "configurations", "Configuration", "Search configuration",
                viewModel.configurationIdProperty(), viewModel.configurationLookupQueryProperty());
        bindLookup(configurationMasterLookupController, "configuration-masters", "Configuration Master",
                "Search configuration master", viewModel.configurationMasterIdProperty(),
                viewModel.configurationMasterLookupQueryProperty());
        bind(serialTypeComboBox, viewModel.serialTypeProperty());
        if (itemTypeComboBox != null) {
            serialSelectionListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.serialTypeProperty().addListener(serialSelectionListener);
        }
        bind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        bind(serialPrefixField, viewModel.serialPrefixProperty());
        bind(serialFromField, viewModel.serialFromProperty());
        bind(serialToField, viewModel.serialToProperty());
        bind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        bindLookup(partMasterLookupController, "parts", "Part Master", "Search part master",
                viewModel.partMasterIdProperty(), viewModel.partMasterLookupQueryProperty());
        bindLookup(partBomLookupController, "part-boms", "Part BOM", "Search part BOM",
                viewModel.partBomIdProperty(), viewModel.partBomLookupQueryProperty());
        bindLookup(rotableLookupController, "rotables", "Rotable", "Search rotable",
                viewModel.rotableIdProperty(), viewModel.rotableLookupQueryProperty());
        bindLookup(operationSpecificationLookupController, "operation-specifications", "Operations",
                "Search operations", viewModel.operationSpecificationIdProperty(),
                viewModel.operationSpecificationLookupQueryProperty());
        bind(rangeRemarksField, viewModel.rangeRemarksProperty());
        bind(serialNumberField, viewModel.serialNumberProperty());
        bind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        bind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        bind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        bind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        bind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        bind(startDateField, viewModel.startDateProperty());
        bind(endDateField, viewModel.endDateProperty());
        scopeVisibilityListener = (observable, oldScope, newScope) -> applyVisibility();
        viewModel.scopeProperty().addListener(scopeVisibilityListener);
        selectRotableModeFromCurrentModel();
        applyVisibility();
        if (typeDriven) {
            editorTypeListener = (observable, oldType, newType) -> applyEditorType();
            viewModel.targetTypeProperty().addListener(editorTypeListener);
            applyEditorType();
        }
    }

    private void applyEditorType() {
        String type = viewModel.targetTypeProperty().get();
        if (targetTypeLabel != null) targetTypeLabel.setText("Effectivity Type");
        if (targetTypeComboBox != null && type != null && !type.isBlank()
                && !targetTypeComboBox.getItems().contains(type)) targetTypeComboBox.getItems().add(type);
        if (editorTypeHint != null) {
            setVisible(editorTypeHint, true);
            editorTypeHint.setText("main".equals(EffectivityEditorTypes.tabFor(type))
                    ? "Choose the subject of this effectivity. Its matching editor opens automatically. Business Partner restrictions are maintained in Links."
                    : EffectivityEditorTypes.label(type) + " Effectivity is ready in the matching tab. Validity, Remarks, Audit and Links remain shared. Switching types preserves entered values; review existing criteria before saving.");
        }
        if (editorSectionTitle != null) editorSectionTitle.setText(EffectivityEditorTypes.label(type) + " Effectivity");
        setVisible(rotableSelectionModeGroup, false);
    }

    @FXML private void onMainTabClicked() { showTab(mainTabContent); }
    @FXML private void onPartSerialTabClicked() { showTab(partSerialTabContent); }
    @FXML private void onOperatorTabClicked() { showTab(operatorTabContent); }
    @FXML private void onValidityTabClicked() { showTab(validityTabContent); }
    @FXML private void onRemarksTabClicked() { showTab(remarksTabContent); }
    @FXML private void onAuditTabClicked() { showTab(auditTabContent); }

    private void configureTabs() {
        ToggleGroup group = new ToggleGroup();
        for (ToggleButton button : new ToggleButton[]{mainTabButton, partSerialTabButton, operatorTabButton, validityTabButton, remarksTabButton, auditTabButton}) {
            if (button != null) {
                button.setToggleGroup(group);
            }
        }
        group.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
            if (newToggle == null && oldToggle != null) {
                oldToggle.setSelected(true);
            }
        });
        mainTabButton.setSelected(true);
        showTab(mainTabContent);
    }

    private void showTab(Node selectedContent) {
        for (Node content : new Node[]{mainTabContent, partSerialTabContent, operatorTabContent, validityTabContent, remarksTabContent, auditTabContent}) {
            if (content == null) {
                continue;
            }
            boolean selected = content == selectedContent;
            content.setManaged(selected);
            content.setVisible(selected);
        }
    }

    private void unbindCurrentViewModel() {
        if (editorTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(editorTypeListener);
            editorTypeListener = null;
        }
        if (serialSelectionListener != null) {
            viewModel.serialTypeProperty().removeListener(serialSelectionListener);
            serialSelectionListener = null;
        }
        if (itemTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(itemTypeListener);
            itemTypeListener = null;
        }
        unbind(itemTypeComboBox, viewModel.targetTypeProperty());
        if (scopeVisibilityListener != null) {
            viewModel.scopeProperty().removeListener(scopeVisibilityListener);
            scopeVisibilityListener = null;
        }
        if (parentEffectivityListener != null) {
            viewModel.parentEffectivityIdProperty().removeListener(parentEffectivityListener);
            parentEffectivityListener = null;
        }
        unbind(scopeComboBox, viewModel.scopeProperty());
        unbind(codeField, viewModel.codeProperty());
        unbind(titleField, viewModel.titleProperty());
        unbind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        unbind(targetTypeComboBox, viewModel.targetTypeProperty());
        unbind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        unbind(sequenceNumberField, viewModel.sequenceNumberProperty());
        unbind(serialTypeComboBox, viewModel.serialTypeProperty());
        unbind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        unbind(serialPrefixField, viewModel.serialPrefixProperty());
        unbind(serialFromField, viewModel.serialFromProperty());
        unbind(serialToField, viewModel.serialToProperty());
        unbind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        unbind(rangeRemarksField, viewModel.rangeRemarksProperty());
        unbind(serialNumberField, viewModel.serialNumberProperty());
        unbind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        unbind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        unbind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        unbind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        unbind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        unbind(startDateField, viewModel.startDateProperty());
        unbind(endDateField, viewModel.endDateProperty());
    }

    private void applyVisibility() {
        for (Node node : new Node[]{
                parentEffectivityLookupGroup, maintenanceTaskLookupGroup, taskCardLookupGroup,
                requirementLookupGroup, maintenanceProgramLookupGroup, deferredMaintenanceLookupGroup,
                publicationLookupGroup, configurationLookupGroup, configurationMasterLookupGroup,
                partMasterLookupGroup, partBomLookupGroup, rotableLookupGroup,
                rotableSelectionModeGroup, serialTypeGroup, serialPrefixGroup, serialFromGroup, serialToGroup,
                serialNumberGroup, rangeRemarksGroup
        }) {
            setVisible(node, true);
        }
        applyRotableSelectionMode();
        applyItemTypeVisibility();
        if (typeDriven) applyEditorType();
    }

    private void applyItemTypeVisibility() {
        if (itemTypeComboBox == null) return;
        String type = viewModel.targetTypeProperty().get();
        boolean software = "Software".equals(type);
        boolean rotable = java.util.List.of("Rotable", "Aircraft", "Component").contains(type == null ? "" : type);
        boolean part = itemTypeComboBox.getItems().contains(type) && !software && !rotable;
        if (software && configurationLookupController != null) {
            configurationLookupController.configure("configurations", "Software Configuration",
                    "Search software configuration", java.util.List.of(
                            new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                                    "Functional Position Type", "E", "Software")));
        }
        if (part && partMasterLookupController != null) {
            String material = "OtherMaterial".equals(type) ? "Other" : type;
            boolean filter = !java.util.List.of("Part", "Hardware").contains(type);
            partMasterLookupController.configure("parts", "Part Master", "Search " + type,
                    filter ? java.util.List.of(new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                            "Material Class", "E", material)) : java.util.List.of());
        }
        setVisible(softwareLookupGroup, software);
        setVisible(rotableLookupGroup, rotable);
        setVisible(partMasterLookupGroup, part);
        boolean serials = part || rotable;
        String selection = viewModel.serialTypeProperty().get();
        boolean range = serials && "Range".equals(selection);
        setVisible(serialTypeGroup, serials);
        setVisible(serialNumberTypeGroup, serials);
        setVisible(serialNumberGroup, serials && ("Single".equals(selection) || "Pattern".equals(selection)));
        setVisible(serialPrefixGroup, serials);
        setVisible(serialFromGroup, range);
        setVisible(serialToGroup, range);
        setVisible(rangeShiftGroup, range);
        setVisible(rangeRemarksGroup, serials);
    }

    private void configureRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        chooseRotableCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                choosePartSerialCheckBox.setSelected(false);
            } else if (!choosePartSerialCheckBox.isSelected()) {
                chooseRotableCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        choosePartSerialCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                chooseRotableCheckBox.setSelected(false);
            } else if (!chooseRotableCheckBox.isSelected()) {
                choosePartSerialCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        chooseRotableCheckBox.setSelected(true);
        applyRotableSelectionMode();
    }

    private void selectRotableModeFromCurrentModel() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null || viewModel == null) {
            return;
        }
        boolean hasRotable = hasText(viewModel.rotableIdProperty().get()) || hasText(viewModel.rotableLookupQueryProperty().get());
        boolean hasPartSerial = hasText(viewModel.partMasterIdProperty().get())
                || hasText(viewModel.partMasterLookupQueryProperty().get())
                || hasText(viewModel.serialNumberProperty().get())
                || hasText(viewModel.serialPrefixProperty().get())
                || hasText(viewModel.serialFromProperty().get())
                || hasText(viewModel.serialToProperty().get())
                || hasText(viewModel.rangeRemarksProperty().get());
        choosePartSerialCheckBox.setSelected(!hasRotable && hasPartSerial);
        chooseRotableCheckBox.setSelected(hasRotable || !hasPartSerial);
    }

    private void applyRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        boolean chooseRotable = chooseRotableCheckBox.isSelected();
        setVisible(rotableLookupGroup, chooseRotable);
        setVisible(partMasterLookupGroup, !chooseRotable);
        setVisible(serialTypeGroup, !chooseRotable);
        setVisible(serialNumberGroup, !chooseRotable);
        setVisible(serialPrefixGroup, !chooseRotable);
        setVisible(serialFromGroup, !chooseRotable);
        setVisible(serialToGroup, !chooseRotable);
        setVisible(rangeRemarksGroup, !chooseRotable);
    }

    private boolean hasText(String value) {
        return value != null && !value.isBlank();
    }

    private void setVisible(Node node, boolean visible) {
        if (node == null) {
            return;
        }
        node.setVisible(visible);
        node.setManaged(visible);
    }

    private void bind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().bindBidirectional(property);
        }
    }

    private void unbind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().unbindBidirectional(property);
        }
    }

    private void bind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().bindBidirectional(property);
        }
    }

    private void unbind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().unbindBidirectional(property);
        }
    }

    private void bind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().bindBidirectional(property);
        }
    }

    private void unbind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().unbindBidirectional(property);
        }
    }

    private void setItems(ComboBox<String> control, java.util.List<String> items) {
        if (control != null) {
            control.getItems().setAll(items);
        }
    }

    private void bindLookup(
            DbSearchLookupFieldController controller,
            String definitionKey,
            String label,
            String prompt,
            StringProperty selectedId,
            StringProperty query
    ) {
        if (controller != null) {
            LookupFieldBinding.bind(controller, definitionKey, label, prompt, selectedId, query);
        }
    }
}

Effectivity / Rotable

GUI review pending

Rotable link.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Rotable
Shared form reference · 2026-10-10T12:33:35.937020 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Rotable
Static JavaFX layout · 1280 × 602 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (13)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Relationship Type
relationshipTypeComboBox
ComboBox
relationshipTypeProperty
Review requiredness, validation and display.
Item Type
itemTypeComboBox
ComboBox
targetTypeProperty
Review requiredness, validation and display.
rotable
rotableLookup
UUID search lookup
rotableIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
part Master
partMasterLookup
UUID search lookup
partMasterIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
configuration
configurationLookup
UUID search lookup
configurationIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Serial Selection
serialTypeComboBox
ComboBox
serialTypeProperty
Review requiredness, validation and display.
Serial Number / Pattern
serialNumberField
TextField
serialNumberProperty
Review requiredness, validation and display.
Serial Number Type
serialNumberTypeComboBox
ComboBox
serialNumberTypeProperty
Review requiredness, validation and display.
Serial Prefix
serialPrefixField
TextField
serialPrefixProperty
promptText=Optional prefix, e.g. ENG-
Range Shift
rangeShiftComboBox
ComboBox
rangeShiftProperty
Review requiredness, validation and display.
Serial From (inclusive)
serialFromField
TextField
serialFromProperty
promptText=First serial, e.g. 1001
Serial To (inclusive)
serialToField
TextField
serialToProperty
promptText=Last serial, e.g. 1050
Range Remarks
rangeRemarksField
TextField
rangeRemarksProperty
Review requiredness, validation and display.
Source evidence

Tab key: effectivity--rotable
Section: Entire form
FXML SHA-256: 45f67477b10c67ef2880b66d4a384be24e2c9cb86fc3e8895316150dae48efe7

coflyr-fe/src/main/resources/com/coflyr/fe/shared/effectivity/views/effectivity-rotable-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      styleClass="standard-section-flat"
      VBox.vgrow="ALWAYS">
    <children>
        <Label fx:id="editorSectionTitle" styleClass="section-title" text="Hardware, Software and Material Applicability"/>
        <Label text="Select a saved item. Part selection may cover all serials, one serial, or a range. Software uses a Software configuration position." wrapText="true" styleClass="field-hint"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Relationship Type"/>
                        <ComboBox fx:id="relationshipTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="rotableSelectionModeGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <Label styleClass="field-label" text="Item Type"/>
                        <ComboBox fx:id="itemTypeComboBox" maxWidth="Infinity" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="rotableLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2">
                    <children>
                        <fx:include fx:id="rotableLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
                <VBox fx:id="partMasterLookupGroup" fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3">
                    <children>
                        <fx:include fx:id="partMasterLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
                <VBox fx:id="softwareLookupGroup" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="3">
                    <children>
                        <fx:include fx:id="configurationLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
                <VBox fx:id="serialTypeGroup" styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="4">
                    <children>
                        <Label styleClass="field-label" text="Serial Selection"/>
                        <ComboBox fx:id="serialTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="serialNumberGroup" styleClass="field-group" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="4">
                    <children>
                        <Label styleClass="field-label" text="Serial Number / Pattern"/>
                        <TextField fx:id="serialNumberField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="serialNumberTypeGroup" styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="5">
                    <children>
                        <Label styleClass="field-label" text="Serial Number Type"/>
                        <ComboBox fx:id="serialNumberTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="serialPrefixGroup" styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="5">
                    <children>
                        <Label styleClass="field-label" text="Serial Prefix"/>
                        <TextField fx:id="serialPrefixField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" promptText="Optional prefix, e.g. ENG-"/>
                    </children>
                </VBox>
                <VBox fx:id="rangeShiftGroup" styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="5">
                    <children>
                        <Label styleClass="field-label" text="Range Shift"/>
                        <ComboBox fx:id="rangeShiftComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="serialFromGroup" styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="6">
                    <children>
                        <Label styleClass="field-label" text="Serial From (inclusive)"/>
                        <TextField fx:id="serialFromField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" promptText="First serial, e.g. 1001"/>
                    </children>
                </VBox>
                <VBox fx:id="serialToGroup" styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="6">
                    <children>
                        <Label styleClass="field-label" text="Serial To (inclusive)"/>
                        <TextField fx:id="serialToField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" promptText="Last serial, e.g. 1050"/>
                    </children>
                </VBox>
                <VBox fx:id="rangeRemarksGroup" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="7">
                    <children>
                        <Label styleClass="field-label" text="Range Remarks"/>
                        <TextField fx:id="rangeRemarksField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController
package com.coflyr.fe.shared.effectivity.controllers;

import com.coflyr.fe.shared.effectivity.viewmodels.EffectivityViewModel;
import com.coflyr.fe.shared.effectivity.EffectivityEditorTypes;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.beans.value.ChangeListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.util.StringConverter;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EffectivitySectionController implements com.coflyr.fe.config.DisposableView {
    private EffectivityViewModel viewModel;
    private boolean bound;
    private boolean typeDriven;
    private ChangeListener<String> editorTypeListener;
    @FXML private Label targetTypeLabel;
    @FXML private Label editorTypeHint;
    @FXML private Label editorSectionTitle;
    private ChangeListener<String> scopeVisibilityListener;
    private ChangeListener<String> parentEffectivityListener;
    private ChangeListener<String> itemTypeListener;
    private ChangeListener<String> serialSelectionListener;
    @FXML private Node serialNumberTypeGroup;
    @FXML private Node rangeShiftGroup;
    @FXML private ComboBox<String> itemTypeComboBox;
    @FXML private Node softwareLookupGroup;

    @FXML private ToggleButton mainTabButton;
    @FXML private ToggleButton partSerialTabButton;
    @FXML private ToggleButton operatorTabButton;
    @FXML private ToggleButton validityTabButton;
    @FXML private ToggleButton remarksTabButton;
    @FXML private ToggleButton auditTabButton;
    @FXML private Node mainTabContent;
    @FXML private Node partSerialTabContent;
    @FXML private Node operatorTabContent;
    @FXML private Node validityTabContent;
    @FXML private Node remarksTabContent;
    @FXML private Node auditTabContent;
    @FXML private ComboBox<String> scopeComboBox;
    @FXML private Node parentEffectivityLookupGroup;
    @FXML private Node maintenanceTaskLookupGroup;
    @FXML private Node taskCardLookupGroup;
    @FXML private Node requirementLookupGroup;
    @FXML private Node maintenanceProgramLookupGroup;
    @FXML private Node deferredMaintenanceLookupGroup;
    @FXML private Node publicationLookupGroup;
    @FXML private Node configurationLookupGroup;
    @FXML private Node configurationMasterLookupGroup;
    @FXML private DbSearchLookupFieldController parentEffectivityLookupController;
    @FXML private TextField codeField;
    @FXML private TextField titleField;
    @FXML private ComboBox<String> relationshipTypeComboBox;
    @FXML private ComboBox<String> targetTypeComboBox;
    @FXML private ComboBox<String> nodeTypeComboBox;
    @FXML private TextField sequenceNumberField;
    @FXML private DbSearchLookupFieldController maintenanceTaskLookupController;
    @FXML private DbSearchLookupFieldController taskCardLookupController;
    @FXML private DbSearchLookupFieldController requirementLookupController;
    @FXML private DbSearchLookupFieldController maintenanceProgramMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredMaintenanceMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredDefectMasterLookupController;
    @FXML private DbSearchLookupFieldController learningActivityLookupController;
    @FXML private DbSearchLookupFieldController trainingNeedAnalysisLookupController;
    @FXML private DbSearchLookupFieldController publicationMasterLookupController;
    @FXML private DbSearchLookupFieldController configurationLookupController;
    @FXML private DbSearchLookupFieldController configurationMasterLookupController;
    @FXML private ComboBox<String> serialTypeComboBox;
    @FXML private ComboBox<String> serialNumberTypeComboBox;
    @FXML private TextField serialPrefixField;
    @FXML private TextField serialFromField;
    @FXML private TextField serialToField;
    @FXML private ComboBox<String> rangeShiftComboBox;
    @FXML private Node partMasterLookupGroup;
    @FXML private Node partBomLookupGroup;
    @FXML private Node rotableLookupGroup;
    @FXML private Node rotableSelectionModeGroup;
    @FXML private CheckBox chooseRotableCheckBox;
    @FXML private CheckBox choosePartSerialCheckBox;
    @FXML private Node serialTypeGroup;
    @FXML private Node serialPrefixGroup;
    @FXML private Node serialFromGroup;
    @FXML private Node serialToGroup;
    @FXML private Node serialNumberGroup;
    @FXML private Node rangeRemarksGroup;
    @FXML private DbSearchLookupFieldController partMasterLookupController;
    @FXML private DbSearchLookupFieldController partBomLookupController;
    @FXML private DbSearchLookupFieldController rotableLookupController;
    @FXML private DbSearchLookupFieldController operationSpecificationLookupController;
    @FXML private TextField rangeRemarksField;
    @FXML private TextField serialNumberField;
    @FXML private CheckBox appliesToFlightOperationsCheckBox;
    @FXML private CheckBox appliesToGroundOperationsCheckBox;
    @FXML private CheckBox appliesToLineMaintenanceCheckBox;
    @FXML private CheckBox appliesToBaseMaintenanceCheckBox;
    @FXML private CheckBox appliesToComponentMaintenanceCheckBox;
    @FXML private TextField startDateField;
    @FXML private TextField endDateField;

    public EffectivitySectionController(EffectivityViewModel viewModel) {
        this.viewModel = viewModel;
    }

    public void setTypeDriven(boolean typeDriven) {
        this.typeDriven = typeDriven;
    }

    @Override public void disposeView() {
        if (bound) unbindCurrentViewModel();
        bound = false;
    }

    @FXML
    private void initialize() {
        if (mainTabButton != null) {
            configureTabs();
        }
        configureRotableSelectionMode();
    }

    public void bindTo(EffectivityViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        if (bound && viewModel == this.viewModel) {
            return;
        }
        if (bound) unbindCurrentViewModel();
        this.viewModel = viewModel;
        bound = true;

        setItems(scopeComboBox, viewModel.scopes());
        setItems(relationshipTypeComboBox, viewModel.relationshipTypes());
        setItems(targetTypeComboBox, viewModel.targetTypes());
        if (typeDriven && targetTypeComboBox != null) {
            setItems(targetTypeComboBox, EffectivityEditorTypes.TYPES);
            targetTypeComboBox.setConverter(new StringConverter<>() {
                @Override public String toString(String value) { return EffectivityEditorTypes.label(value); }
                @Override public String fromString(String label) {
                    return EffectivityEditorTypes.TYPES.stream()
                            .filter(type -> EffectivityEditorTypes.label(type).equals(label))
                            .findFirst().orElse(label);
                }
            });
        }
        setItems(nodeTypeComboBox, viewModel.nodeTypes());
        setItems(serialTypeComboBox, viewModel.serialTypes());
        setItems(serialNumberTypeComboBox, viewModel.serialNumberTypes());
        setItems(rangeShiftComboBox, viewModel.rangeShifts());

        bind(scopeComboBox, viewModel.scopeProperty());
        if (scopeComboBox != null) scopeComboBox.setDisable(true);
        bind(codeField, viewModel.codeProperty());
        bindLookup(deferredDefectMasterLookupController, "deferred-defects", "Deferred Defect",
                "Search deferred defect", viewModel.deferredDefectMasterIdProperty(), viewModel.deferredDefectLookupQueryProperty());
        bindLookup(learningActivityLookupController, "learningactivity", "Learning Activity",
                "Search learning activity", viewModel.learningActivityIdProperty(), viewModel.learningActivityLookupQueryProperty());
        bindLookup(trainingNeedAnalysisLookupController, "trainingneedanalysis", "Training Need Analysis",
                "Search training need analysis", viewModel.trainingNeedAnalysisIdProperty(), viewModel.trainingNeedAnalysisLookupQueryProperty());
        bind(titleField, viewModel.titleProperty());
        bindLookup(parentEffectivityLookupController, "effectivity", "Existing Effectivity", "Search existing effectivity",
                viewModel.parentEffectivityIdProperty(), viewModel.parentEffectivityLookupQueryProperty());
        parentEffectivityListener = (observable, oldValue, newValue) -> {
            if (newValue != null && !newValue.isBlank()) {
                viewModel.nodeTypeProperty().set("LinkedObject");
            }
        };
        viewModel.parentEffectivityIdProperty().addListener(parentEffectivityListener);
        if (!viewModel.parentEffectivityIdProperty().get().isBlank()) {
            viewModel.nodeTypeProperty().set("LinkedObject");
        }
        bind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        bind(targetTypeComboBox, viewModel.targetTypeProperty());
        if (itemTypeComboBox != null) {
            setItems(itemTypeComboBox, java.util.List.of("Rotable", "Aircraft", "Component", "Part", "Hardware", "Consumable",
                    "Expendable", "Repairable", "Tool", "RawMaterial", "OtherMaterial", "Software"));
            bind(itemTypeComboBox, viewModel.targetTypeProperty());
            itemTypeListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.targetTypeProperty().addListener(itemTypeListener);
            applyItemTypeVisibility();
        }
        bind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        bind(sequenceNumberField, viewModel.sequenceNumberProperty());
        bindLookup(maintenanceTaskLookupController, "maintenance-tasks", "Maintenance Task", "Search maintenance task",
                viewModel.maintenanceTaskIdProperty(), viewModel.maintenanceTaskLookupQueryProperty());
        bindLookup(taskCardLookupController, "task-cards", "Task Card", "Search task card",
                viewModel.taskCardIdProperty(), viewModel.taskCardLookupQueryProperty());
        bindLookup(requirementLookupController, "requirements", "Requirement", "Search requirement",
                viewModel.requirementIdProperty(), viewModel.requirementLookupQueryProperty());
        bindLookup(maintenanceProgramMasterLookupController, "maintenance-programs", "Maintenance Program",
                "Search maintenance program", viewModel.maintenanceProgramMasterIdProperty(),
                viewModel.maintenanceProgramMasterLookupQueryProperty());
        bindLookup(deferredMaintenanceMasterLookupController, "deferred-maintenance", "Deferred Maintenance",
                "Search deferred maintenance", viewModel.deferredMaintenanceMasterIdProperty(),
                viewModel.deferredMaintenanceMasterLookupQueryProperty());
        bindLookup(publicationMasterLookupController, "publications", "Publication", "Search publication",
                viewModel.publicationMasterIdProperty(), viewModel.publicationMasterLookupQueryProperty());
        bindLookup(configurationLookupController, "configurations", "Configuration", "Search configuration",
                viewModel.configurationIdProperty(), viewModel.configurationLookupQueryProperty());
        bindLookup(configurationMasterLookupController, "configuration-masters", "Configuration Master",
                "Search configuration master", viewModel.configurationMasterIdProperty(),
                viewModel.configurationMasterLookupQueryProperty());
        bind(serialTypeComboBox, viewModel.serialTypeProperty());
        if (itemTypeComboBox != null) {
            serialSelectionListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.serialTypeProperty().addListener(serialSelectionListener);
        }
        bind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        bind(serialPrefixField, viewModel.serialPrefixProperty());
        bind(serialFromField, viewModel.serialFromProperty());
        bind(serialToField, viewModel.serialToProperty());
        bind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        bindLookup(partMasterLookupController, "parts", "Part Master", "Search part master",
                viewModel.partMasterIdProperty(), viewModel.partMasterLookupQueryProperty());
        bindLookup(partBomLookupController, "part-boms", "Part BOM", "Search part BOM",
                viewModel.partBomIdProperty(), viewModel.partBomLookupQueryProperty());
        bindLookup(rotableLookupController, "rotables", "Rotable", "Search rotable",
                viewModel.rotableIdProperty(), viewModel.rotableLookupQueryProperty());
        bindLookup(operationSpecificationLookupController, "operation-specifications", "Operations",
                "Search operations", viewModel.operationSpecificationIdProperty(),
                viewModel.operationSpecificationLookupQueryProperty());
        bind(rangeRemarksField, viewModel.rangeRemarksProperty());
        bind(serialNumberField, viewModel.serialNumberProperty());
        bind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        bind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        bind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        bind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        bind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        bind(startDateField, viewModel.startDateProperty());
        bind(endDateField, viewModel.endDateProperty());
        scopeVisibilityListener = (observable, oldScope, newScope) -> applyVisibility();
        viewModel.scopeProperty().addListener(scopeVisibilityListener);
        selectRotableModeFromCurrentModel();
        applyVisibility();
        if (typeDriven) {
            editorTypeListener = (observable, oldType, newType) -> applyEditorType();
            viewModel.targetTypeProperty().addListener(editorTypeListener);
            applyEditorType();
        }
    }

    private void applyEditorType() {
        String type = viewModel.targetTypeProperty().get();
        if (targetTypeLabel != null) targetTypeLabel.setText("Effectivity Type");
        if (targetTypeComboBox != null && type != null && !type.isBlank()
                && !targetTypeComboBox.getItems().contains(type)) targetTypeComboBox.getItems().add(type);
        if (editorTypeHint != null) {
            setVisible(editorTypeHint, true);
            editorTypeHint.setText("main".equals(EffectivityEditorTypes.tabFor(type))
                    ? "Choose the subject of this effectivity. Its matching editor opens automatically. Business Partner restrictions are maintained in Links."
                    : EffectivityEditorTypes.label(type) + " Effectivity is ready in the matching tab. Validity, Remarks, Audit and Links remain shared. Switching types preserves entered values; review existing criteria before saving.");
        }
        if (editorSectionTitle != null) editorSectionTitle.setText(EffectivityEditorTypes.label(type) + " Effectivity");
        setVisible(rotableSelectionModeGroup, false);
    }

    @FXML private void onMainTabClicked() { showTab(mainTabContent); }
    @FXML private void onPartSerialTabClicked() { showTab(partSerialTabContent); }
    @FXML private void onOperatorTabClicked() { showTab(operatorTabContent); }
    @FXML private void onValidityTabClicked() { showTab(validityTabContent); }
    @FXML private void onRemarksTabClicked() { showTab(remarksTabContent); }
    @FXML private void onAuditTabClicked() { showTab(auditTabContent); }

    private void configureTabs() {
        ToggleGroup group = new ToggleGroup();
        for (ToggleButton button : new ToggleButton[]{mainTabButton, partSerialTabButton, operatorTabButton, validityTabButton, remarksTabButton, auditTabButton}) {
            if (button != null) {
                button.setToggleGroup(group);
            }
        }
        group.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
            if (newToggle == null && oldToggle != null) {
                oldToggle.setSelected(true);
            }
        });
        mainTabButton.setSelected(true);
        showTab(mainTabContent);
    }

    private void showTab(Node selectedContent) {
        for (Node content : new Node[]{mainTabContent, partSerialTabContent, operatorTabContent, validityTabContent, remarksTabContent, auditTabContent}) {
            if (content == null) {
                continue;
            }
            boolean selected = content == selectedContent;
            content.setManaged(selected);
            content.setVisible(selected);
        }
    }

    private void unbindCurrentViewModel() {
        if (editorTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(editorTypeListener);
            editorTypeListener = null;
        }
        if (serialSelectionListener != null) {
            viewModel.serialTypeProperty().removeListener(serialSelectionListener);
            serialSelectionListener = null;
        }
        if (itemTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(itemTypeListener);
            itemTypeListener = null;
        }
        unbind(itemTypeComboBox, viewModel.targetTypeProperty());
        if (scopeVisibilityListener != null) {
            viewModel.scopeProperty().removeListener(scopeVisibilityListener);
            scopeVisibilityListener = null;
        }
        if (parentEffectivityListener != null) {
            viewModel.parentEffectivityIdProperty().removeListener(parentEffectivityListener);
            parentEffectivityListener = null;
        }
        unbind(scopeComboBox, viewModel.scopeProperty());
        unbind(codeField, viewModel.codeProperty());
        unbind(titleField, viewModel.titleProperty());
        unbind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        unbind(targetTypeComboBox, viewModel.targetTypeProperty());
        unbind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        unbind(sequenceNumberField, viewModel.sequenceNumberProperty());
        unbind(serialTypeComboBox, viewModel.serialTypeProperty());
        unbind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        unbind(serialPrefixField, viewModel.serialPrefixProperty());
        unbind(serialFromField, viewModel.serialFromProperty());
        unbind(serialToField, viewModel.serialToProperty());
        unbind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        unbind(rangeRemarksField, viewModel.rangeRemarksProperty());
        unbind(serialNumberField, viewModel.serialNumberProperty());
        unbind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        unbind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        unbind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        unbind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        unbind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        unbind(startDateField, viewModel.startDateProperty());
        unbind(endDateField, viewModel.endDateProperty());
    }

    private void applyVisibility() {
        for (Node node : new Node[]{
                parentEffectivityLookupGroup, maintenanceTaskLookupGroup, taskCardLookupGroup,
                requirementLookupGroup, maintenanceProgramLookupGroup, deferredMaintenanceLookupGroup,
                publicationLookupGroup, configurationLookupGroup, configurationMasterLookupGroup,
                partMasterLookupGroup, partBomLookupGroup, rotableLookupGroup,
                rotableSelectionModeGroup, serialTypeGroup, serialPrefixGroup, serialFromGroup, serialToGroup,
                serialNumberGroup, rangeRemarksGroup
        }) {
            setVisible(node, true);
        }
        applyRotableSelectionMode();
        applyItemTypeVisibility();
        if (typeDriven) applyEditorType();
    }

    private void applyItemTypeVisibility() {
        if (itemTypeComboBox == null) return;
        String type = viewModel.targetTypeProperty().get();
        boolean software = "Software".equals(type);
        boolean rotable = java.util.List.of("Rotable", "Aircraft", "Component").contains(type == null ? "" : type);
        boolean part = itemTypeComboBox.getItems().contains(type) && !software && !rotable;
        if (software && configurationLookupController != null) {
            configurationLookupController.configure("configurations", "Software Configuration",
                    "Search software configuration", java.util.List.of(
                            new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                                    "Functional Position Type", "E", "Software")));
        }
        if (part && partMasterLookupController != null) {
            String material = "OtherMaterial".equals(type) ? "Other" : type;
            boolean filter = !java.util.List.of("Part", "Hardware").contains(type);
            partMasterLookupController.configure("parts", "Part Master", "Search " + type,
                    filter ? java.util.List.of(new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                            "Material Class", "E", material)) : java.util.List.of());
        }
        setVisible(softwareLookupGroup, software);
        setVisible(rotableLookupGroup, rotable);
        setVisible(partMasterLookupGroup, part);
        boolean serials = part || rotable;
        String selection = viewModel.serialTypeProperty().get();
        boolean range = serials && "Range".equals(selection);
        setVisible(serialTypeGroup, serials);
        setVisible(serialNumberTypeGroup, serials);
        setVisible(serialNumberGroup, serials && ("Single".equals(selection) || "Pattern".equals(selection)));
        setVisible(serialPrefixGroup, serials);
        setVisible(serialFromGroup, range);
        setVisible(serialToGroup, range);
        setVisible(rangeShiftGroup, range);
        setVisible(rangeRemarksGroup, serials);
    }

    private void configureRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        chooseRotableCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                choosePartSerialCheckBox.setSelected(false);
            } else if (!choosePartSerialCheckBox.isSelected()) {
                chooseRotableCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        choosePartSerialCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                chooseRotableCheckBox.setSelected(false);
            } else if (!chooseRotableCheckBox.isSelected()) {
                choosePartSerialCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        chooseRotableCheckBox.setSelected(true);
        applyRotableSelectionMode();
    }

    private void selectRotableModeFromCurrentModel() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null || viewModel == null) {
            return;
        }
        boolean hasRotable = hasText(viewModel.rotableIdProperty().get()) || hasText(viewModel.rotableLookupQueryProperty().get());
        boolean hasPartSerial = hasText(viewModel.partMasterIdProperty().get())
                || hasText(viewModel.partMasterLookupQueryProperty().get())
                || hasText(viewModel.serialNumberProperty().get())
                || hasText(viewModel.serialPrefixProperty().get())
                || hasText(viewModel.serialFromProperty().get())
                || hasText(viewModel.serialToProperty().get())
                || hasText(viewModel.rangeRemarksProperty().get());
        choosePartSerialCheckBox.setSelected(!hasRotable && hasPartSerial);
        chooseRotableCheckBox.setSelected(hasRotable || !hasPartSerial);
    }

    private void applyRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        boolean chooseRotable = chooseRotableCheckBox.isSelected();
        setVisible(rotableLookupGroup, chooseRotable);
        setVisible(partMasterLookupGroup, !chooseRotable);
        setVisible(serialTypeGroup, !chooseRotable);
        setVisible(serialNumberGroup, !chooseRotable);
        setVisible(serialPrefixGroup, !chooseRotable);
        setVisible(serialFromGroup, !chooseRotable);
        setVisible(serialToGroup, !chooseRotable);
        setVisible(rangeRemarksGroup, !chooseRotable);
    }

    private boolean hasText(String value) {
        return value != null && !value.isBlank();
    }

    private void setVisible(Node node, boolean visible) {
        if (node == null) {
            return;
        }
        node.setVisible(visible);
        node.setManaged(visible);
    }

    private void bind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().bindBidirectional(property);
        }
    }

    private void unbind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().unbindBidirectional(property);
        }
    }

    private void bind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().bindBidirectional(property);
        }
    }

    private void unbind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().unbindBidirectional(property);
        }
    }

    private void bind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().bindBidirectional(property);
        }
    }

    private void unbind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().unbindBidirectional(property);
        }
    }

    private void setItems(ComboBox<String> control, java.util.List<String> items) {
        if (control != null) {
            control.getItems().setAll(items);
        }
    }

    private void bindLookup(
            DbSearchLookupFieldController controller,
            String definitionKey,
            String label,
            String prompt,
            StringProperty selectedId,
            StringProperty query
    ) {
        if (controller != null) {
            LookupFieldBinding.bind(controller, definitionKey, label, prompt, selectedId, query);
        }
    }
}

Effectivity / Operations

GUI review pending

Operator and operation specification link.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Operations
Shared form reference · 2026-10-10T12:32:48.650037 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Operations
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (7)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Relationship Type
relationshipTypeComboBox
ComboBox
relationshipTypeProperty
Review requiredness, validation and display.
Flight Operations
appliesToFlightOperationsCheckBox
CheckBox
appliesToFlightOperationsProperty
Review requiredness, validation and display.
Ground Operations
appliesToGroundOperationsCheckBox
CheckBox
appliesToGroundOperationsProperty
Review requiredness, validation and display.
Line Maintenance
appliesToLineMaintenanceCheckBox
CheckBox
appliesToLineMaintenanceProperty
Review requiredness, validation and display.
Base Maintenance
appliesToBaseMaintenanceCheckBox
CheckBox
appliesToBaseMaintenanceProperty
Review requiredness, validation and display.
Component Maintenance
appliesToComponentMaintenanceCheckBox
CheckBox
appliesToComponentMaintenanceProperty
Review requiredness, validation and display.
operation Specification
operationSpecificationLookup
UUID search lookup
operationSpecificationIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Source evidence

Tab key: effectivity--operations
Section: Entire form
FXML SHA-256: 776ecf9eca9db6abdca6646e39e5ccffe8700ed117c9c7dea5d4e1eb0d8f9bd2

coflyr-fe/src/main/resources/com/coflyr/fe/shared/effectivity/views/effectivity-operations-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.MenuButton?>
<?import javafx.scene.control.CustomMenuItem?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      styleClass="standard-section-flat"
      VBox.vgrow="ALWAYS">
    <children>
        <Label fx:id="editorSectionTitle" styleClass="section-title" text="Operations"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Relationship Type"/>
                        <ComboBox fx:id="relationshipTypeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Operation Applicability"/>
                        <Label styleClass="field-hint" text="Select every operation for which this effectivity is valid." wrapText="true"/>
                        <MenuButton text="Select activities" maxWidth="Infinity" styleClass="text-input">
                            <items>
                                <CustomMenuItem hideOnClick="false"><content><CheckBox fx:id="appliesToFlightOperationsCheckBox" text="Flight Operations"/></content></CustomMenuItem>
                                <CustomMenuItem hideOnClick="false"><content><CheckBox fx:id="appliesToGroundOperationsCheckBox" text="Ground Operations"/></content></CustomMenuItem>
                                <CustomMenuItem hideOnClick="false"><content><CheckBox fx:id="appliesToLineMaintenanceCheckBox" text="Line Maintenance"/></content></CustomMenuItem>
                                <CustomMenuItem hideOnClick="false"><content><CheckBox fx:id="appliesToBaseMaintenanceCheckBox" text="Base Maintenance"/></content></CustomMenuItem>
                                <CustomMenuItem hideOnClick="false"><content><CheckBox fx:id="appliesToComponentMaintenanceCheckBox" text="Component Maintenance"/></content></CustomMenuItem>
                            </items>
                        </MenuButton>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <fx:include fx:id="operationSpecificationLookup" source="../../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController
package com.coflyr.fe.shared.effectivity.controllers;

import com.coflyr.fe.shared.effectivity.viewmodels.EffectivityViewModel;
import com.coflyr.fe.shared.effectivity.EffectivityEditorTypes;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.beans.value.ChangeListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.util.StringConverter;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EffectivitySectionController implements com.coflyr.fe.config.DisposableView {
    private EffectivityViewModel viewModel;
    private boolean bound;
    private boolean typeDriven;
    private ChangeListener<String> editorTypeListener;
    @FXML private Label targetTypeLabel;
    @FXML private Label editorTypeHint;
    @FXML private Label editorSectionTitle;
    private ChangeListener<String> scopeVisibilityListener;
    private ChangeListener<String> parentEffectivityListener;
    private ChangeListener<String> itemTypeListener;
    private ChangeListener<String> serialSelectionListener;
    @FXML private Node serialNumberTypeGroup;
    @FXML private Node rangeShiftGroup;
    @FXML private ComboBox<String> itemTypeComboBox;
    @FXML private Node softwareLookupGroup;

    @FXML private ToggleButton mainTabButton;
    @FXML private ToggleButton partSerialTabButton;
    @FXML private ToggleButton operatorTabButton;
    @FXML private ToggleButton validityTabButton;
    @FXML private ToggleButton remarksTabButton;
    @FXML private ToggleButton auditTabButton;
    @FXML private Node mainTabContent;
    @FXML private Node partSerialTabContent;
    @FXML private Node operatorTabContent;
    @FXML private Node validityTabContent;
    @FXML private Node remarksTabContent;
    @FXML private Node auditTabContent;
    @FXML private ComboBox<String> scopeComboBox;
    @FXML private Node parentEffectivityLookupGroup;
    @FXML private Node maintenanceTaskLookupGroup;
    @FXML private Node taskCardLookupGroup;
    @FXML private Node requirementLookupGroup;
    @FXML private Node maintenanceProgramLookupGroup;
    @FXML private Node deferredMaintenanceLookupGroup;
    @FXML private Node publicationLookupGroup;
    @FXML private Node configurationLookupGroup;
    @FXML private Node configurationMasterLookupGroup;
    @FXML private DbSearchLookupFieldController parentEffectivityLookupController;
    @FXML private TextField codeField;
    @FXML private TextField titleField;
    @FXML private ComboBox<String> relationshipTypeComboBox;
    @FXML private ComboBox<String> targetTypeComboBox;
    @FXML private ComboBox<String> nodeTypeComboBox;
    @FXML private TextField sequenceNumberField;
    @FXML private DbSearchLookupFieldController maintenanceTaskLookupController;
    @FXML private DbSearchLookupFieldController taskCardLookupController;
    @FXML private DbSearchLookupFieldController requirementLookupController;
    @FXML private DbSearchLookupFieldController maintenanceProgramMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredMaintenanceMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredDefectMasterLookupController;
    @FXML private DbSearchLookupFieldController learningActivityLookupController;
    @FXML private DbSearchLookupFieldController trainingNeedAnalysisLookupController;
    @FXML private DbSearchLookupFieldController publicationMasterLookupController;
    @FXML private DbSearchLookupFieldController configurationLookupController;
    @FXML private DbSearchLookupFieldController configurationMasterLookupController;
    @FXML private ComboBox<String> serialTypeComboBox;
    @FXML private ComboBox<String> serialNumberTypeComboBox;
    @FXML private TextField serialPrefixField;
    @FXML private TextField serialFromField;
    @FXML private TextField serialToField;
    @FXML private ComboBox<String> rangeShiftComboBox;
    @FXML private Node partMasterLookupGroup;
    @FXML private Node partBomLookupGroup;
    @FXML private Node rotableLookupGroup;
    @FXML private Node rotableSelectionModeGroup;
    @FXML private CheckBox chooseRotableCheckBox;
    @FXML private CheckBox choosePartSerialCheckBox;
    @FXML private Node serialTypeGroup;
    @FXML private Node serialPrefixGroup;
    @FXML private Node serialFromGroup;
    @FXML private Node serialToGroup;
    @FXML private Node serialNumberGroup;
    @FXML private Node rangeRemarksGroup;
    @FXML private DbSearchLookupFieldController partMasterLookupController;
    @FXML private DbSearchLookupFieldController partBomLookupController;
    @FXML private DbSearchLookupFieldController rotableLookupController;
    @FXML private DbSearchLookupFieldController operationSpecificationLookupController;
    @FXML private TextField rangeRemarksField;
    @FXML private TextField serialNumberField;
    @FXML private CheckBox appliesToFlightOperationsCheckBox;
    @FXML private CheckBox appliesToGroundOperationsCheckBox;
    @FXML private CheckBox appliesToLineMaintenanceCheckBox;
    @FXML private CheckBox appliesToBaseMaintenanceCheckBox;
    @FXML private CheckBox appliesToComponentMaintenanceCheckBox;
    @FXML private TextField startDateField;
    @FXML private TextField endDateField;

    public EffectivitySectionController(EffectivityViewModel viewModel) {
        this.viewModel = viewModel;
    }

    public void setTypeDriven(boolean typeDriven) {
        this.typeDriven = typeDriven;
    }

    @Override public void disposeView() {
        if (bound) unbindCurrentViewModel();
        bound = false;
    }

    @FXML
    private void initialize() {
        if (mainTabButton != null) {
            configureTabs();
        }
        configureRotableSelectionMode();
    }

    public void bindTo(EffectivityViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        if (bound && viewModel == this.viewModel) {
            return;
        }
        if (bound) unbindCurrentViewModel();
        this.viewModel = viewModel;
        bound = true;

        setItems(scopeComboBox, viewModel.scopes());
        setItems(relationshipTypeComboBox, viewModel.relationshipTypes());
        setItems(targetTypeComboBox, viewModel.targetTypes());
        if (typeDriven && targetTypeComboBox != null) {
            setItems(targetTypeComboBox, EffectivityEditorTypes.TYPES);
            targetTypeComboBox.setConverter(new StringConverter<>() {
                @Override public String toString(String value) { return EffectivityEditorTypes.label(value); }
                @Override public String fromString(String label) {
                    return EffectivityEditorTypes.TYPES.stream()
                            .filter(type -> EffectivityEditorTypes.label(type).equals(label))
                            .findFirst().orElse(label);
                }
            });
        }
        setItems(nodeTypeComboBox, viewModel.nodeTypes());
        setItems(serialTypeComboBox, viewModel.serialTypes());
        setItems(serialNumberTypeComboBox, viewModel.serialNumberTypes());
        setItems(rangeShiftComboBox, viewModel.rangeShifts());

        bind(scopeComboBox, viewModel.scopeProperty());
        if (scopeComboBox != null) scopeComboBox.setDisable(true);
        bind(codeField, viewModel.codeProperty());
        bindLookup(deferredDefectMasterLookupController, "deferred-defects", "Deferred Defect",
                "Search deferred defect", viewModel.deferredDefectMasterIdProperty(), viewModel.deferredDefectLookupQueryProperty());
        bindLookup(learningActivityLookupController, "learningactivity", "Learning Activity",
                "Search learning activity", viewModel.learningActivityIdProperty(), viewModel.learningActivityLookupQueryProperty());
        bindLookup(trainingNeedAnalysisLookupController, "trainingneedanalysis", "Training Need Analysis",
                "Search training need analysis", viewModel.trainingNeedAnalysisIdProperty(), viewModel.trainingNeedAnalysisLookupQueryProperty());
        bind(titleField, viewModel.titleProperty());
        bindLookup(parentEffectivityLookupController, "effectivity", "Existing Effectivity", "Search existing effectivity",
                viewModel.parentEffectivityIdProperty(), viewModel.parentEffectivityLookupQueryProperty());
        parentEffectivityListener = (observable, oldValue, newValue) -> {
            if (newValue != null && !newValue.isBlank()) {
                viewModel.nodeTypeProperty().set("LinkedObject");
            }
        };
        viewModel.parentEffectivityIdProperty().addListener(parentEffectivityListener);
        if (!viewModel.parentEffectivityIdProperty().get().isBlank()) {
            viewModel.nodeTypeProperty().set("LinkedObject");
        }
        bind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        bind(targetTypeComboBox, viewModel.targetTypeProperty());
        if (itemTypeComboBox != null) {
            setItems(itemTypeComboBox, java.util.List.of("Rotable", "Aircraft", "Component", "Part", "Hardware", "Consumable",
                    "Expendable", "Repairable", "Tool", "RawMaterial", "OtherMaterial", "Software"));
            bind(itemTypeComboBox, viewModel.targetTypeProperty());
            itemTypeListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.targetTypeProperty().addListener(itemTypeListener);
            applyItemTypeVisibility();
        }
        bind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        bind(sequenceNumberField, viewModel.sequenceNumberProperty());
        bindLookup(maintenanceTaskLookupController, "maintenance-tasks", "Maintenance Task", "Search maintenance task",
                viewModel.maintenanceTaskIdProperty(), viewModel.maintenanceTaskLookupQueryProperty());
        bindLookup(taskCardLookupController, "task-cards", "Task Card", "Search task card",
                viewModel.taskCardIdProperty(), viewModel.taskCardLookupQueryProperty());
        bindLookup(requirementLookupController, "requirements", "Requirement", "Search requirement",
                viewModel.requirementIdProperty(), viewModel.requirementLookupQueryProperty());
        bindLookup(maintenanceProgramMasterLookupController, "maintenance-programs", "Maintenance Program",
                "Search maintenance program", viewModel.maintenanceProgramMasterIdProperty(),
                viewModel.maintenanceProgramMasterLookupQueryProperty());
        bindLookup(deferredMaintenanceMasterLookupController, "deferred-maintenance", "Deferred Maintenance",
                "Search deferred maintenance", viewModel.deferredMaintenanceMasterIdProperty(),
                viewModel.deferredMaintenanceMasterLookupQueryProperty());
        bindLookup(publicationMasterLookupController, "publications", "Publication", "Search publication",
                viewModel.publicationMasterIdProperty(), viewModel.publicationMasterLookupQueryProperty());
        bindLookup(configurationLookupController, "configurations", "Configuration", "Search configuration",
                viewModel.configurationIdProperty(), viewModel.configurationLookupQueryProperty());
        bindLookup(configurationMasterLookupController, "configuration-masters", "Configuration Master",
                "Search configuration master", viewModel.configurationMasterIdProperty(),
                viewModel.configurationMasterLookupQueryProperty());
        bind(serialTypeComboBox, viewModel.serialTypeProperty());
        if (itemTypeComboBox != null) {
            serialSelectionListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.serialTypeProperty().addListener(serialSelectionListener);
        }
        bind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        bind(serialPrefixField, viewModel.serialPrefixProperty());
        bind(serialFromField, viewModel.serialFromProperty());
        bind(serialToField, viewModel.serialToProperty());
        bind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        bindLookup(partMasterLookupController, "parts", "Part Master", "Search part master",
                viewModel.partMasterIdProperty(), viewModel.partMasterLookupQueryProperty());
        bindLookup(partBomLookupController, "part-boms", "Part BOM", "Search part BOM",
                viewModel.partBomIdProperty(), viewModel.partBomLookupQueryProperty());
        bindLookup(rotableLookupController, "rotables", "Rotable", "Search rotable",
                viewModel.rotableIdProperty(), viewModel.rotableLookupQueryProperty());
        bindLookup(operationSpecificationLookupController, "operation-specifications", "Operations",
                "Search operations", viewModel.operationSpecificationIdProperty(),
                viewModel.operationSpecificationLookupQueryProperty());
        bind(rangeRemarksField, viewModel.rangeRemarksProperty());
        bind(serialNumberField, viewModel.serialNumberProperty());
        bind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        bind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        bind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        bind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        bind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        bind(startDateField, viewModel.startDateProperty());
        bind(endDateField, viewModel.endDateProperty());
        scopeVisibilityListener = (observable, oldScope, newScope) -> applyVisibility();
        viewModel.scopeProperty().addListener(scopeVisibilityListener);
        selectRotableModeFromCurrentModel();
        applyVisibility();
        if (typeDriven) {
            editorTypeListener = (observable, oldType, newType) -> applyEditorType();
            viewModel.targetTypeProperty().addListener(editorTypeListener);
            applyEditorType();
        }
    }

    private void applyEditorType() {
        String type = viewModel.targetTypeProperty().get();
        if (targetTypeLabel != null) targetTypeLabel.setText("Effectivity Type");
        if (targetTypeComboBox != null && type != null && !type.isBlank()
                && !targetTypeComboBox.getItems().contains(type)) targetTypeComboBox.getItems().add(type);
        if (editorTypeHint != null) {
            setVisible(editorTypeHint, true);
            editorTypeHint.setText("main".equals(EffectivityEditorTypes.tabFor(type))
                    ? "Choose the subject of this effectivity. Its matching editor opens automatically. Business Partner restrictions are maintained in Links."
                    : EffectivityEditorTypes.label(type) + " Effectivity is ready in the matching tab. Validity, Remarks, Audit and Links remain shared. Switching types preserves entered values; review existing criteria before saving.");
        }
        if (editorSectionTitle != null) editorSectionTitle.setText(EffectivityEditorTypes.label(type) + " Effectivity");
        setVisible(rotableSelectionModeGroup, false);
    }

    @FXML private void onMainTabClicked() { showTab(mainTabContent); }
    @FXML private void onPartSerialTabClicked() { showTab(partSerialTabContent); }
    @FXML private void onOperatorTabClicked() { showTab(operatorTabContent); }
    @FXML private void onValidityTabClicked() { showTab(validityTabContent); }
    @FXML private void onRemarksTabClicked() { showTab(remarksTabContent); }
    @FXML private void onAuditTabClicked() { showTab(auditTabContent); }

    private void configureTabs() {
        ToggleGroup group = new ToggleGroup();
        for (ToggleButton button : new ToggleButton[]{mainTabButton, partSerialTabButton, operatorTabButton, validityTabButton, remarksTabButton, auditTabButton}) {
            if (button != null) {
                button.setToggleGroup(group);
            }
        }
        group.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
            if (newToggle == null && oldToggle != null) {
                oldToggle.setSelected(true);
            }
        });
        mainTabButton.setSelected(true);
        showTab(mainTabContent);
    }

    private void showTab(Node selectedContent) {
        for (Node content : new Node[]{mainTabContent, partSerialTabContent, operatorTabContent, validityTabContent, remarksTabContent, auditTabContent}) {
            if (content == null) {
                continue;
            }
            boolean selected = content == selectedContent;
            content.setManaged(selected);
            content.setVisible(selected);
        }
    }

    private void unbindCurrentViewModel() {
        if (editorTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(editorTypeListener);
            editorTypeListener = null;
        }
        if (serialSelectionListener != null) {
            viewModel.serialTypeProperty().removeListener(serialSelectionListener);
            serialSelectionListener = null;
        }
        if (itemTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(itemTypeListener);
            itemTypeListener = null;
        }
        unbind(itemTypeComboBox, viewModel.targetTypeProperty());
        if (scopeVisibilityListener != null) {
            viewModel.scopeProperty().removeListener(scopeVisibilityListener);
            scopeVisibilityListener = null;
        }
        if (parentEffectivityListener != null) {
            viewModel.parentEffectivityIdProperty().removeListener(parentEffectivityListener);
            parentEffectivityListener = null;
        }
        unbind(scopeComboBox, viewModel.scopeProperty());
        unbind(codeField, viewModel.codeProperty());
        unbind(titleField, viewModel.titleProperty());
        unbind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        unbind(targetTypeComboBox, viewModel.targetTypeProperty());
        unbind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        unbind(sequenceNumberField, viewModel.sequenceNumberProperty());
        unbind(serialTypeComboBox, viewModel.serialTypeProperty());
        unbind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        unbind(serialPrefixField, viewModel.serialPrefixProperty());
        unbind(serialFromField, viewModel.serialFromProperty());
        unbind(serialToField, viewModel.serialToProperty());
        unbind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        unbind(rangeRemarksField, viewModel.rangeRemarksProperty());
        unbind(serialNumberField, viewModel.serialNumberProperty());
        unbind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        unbind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        unbind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        unbind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        unbind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        unbind(startDateField, viewModel.startDateProperty());
        unbind(endDateField, viewModel.endDateProperty());
    }

    private void applyVisibility() {
        for (Node node : new Node[]{
                parentEffectivityLookupGroup, maintenanceTaskLookupGroup, taskCardLookupGroup,
                requirementLookupGroup, maintenanceProgramLookupGroup, deferredMaintenanceLookupGroup,
                publicationLookupGroup, configurationLookupGroup, configurationMasterLookupGroup,
                partMasterLookupGroup, partBomLookupGroup, rotableLookupGroup,
                rotableSelectionModeGroup, serialTypeGroup, serialPrefixGroup, serialFromGroup, serialToGroup,
                serialNumberGroup, rangeRemarksGroup
        }) {
            setVisible(node, true);
        }
        applyRotableSelectionMode();
        applyItemTypeVisibility();
        if (typeDriven) applyEditorType();
    }

    private void applyItemTypeVisibility() {
        if (itemTypeComboBox == null) return;
        String type = viewModel.targetTypeProperty().get();
        boolean software = "Software".equals(type);
        boolean rotable = java.util.List.of("Rotable", "Aircraft", "Component").contains(type == null ? "" : type);
        boolean part = itemTypeComboBox.getItems().contains(type) && !software && !rotable;
        if (software && configurationLookupController != null) {
            configurationLookupController.configure("configurations", "Software Configuration",
                    "Search software configuration", java.util.List.of(
                            new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                                    "Functional Position Type", "E", "Software")));
        }
        if (part && partMasterLookupController != null) {
            String material = "OtherMaterial".equals(type) ? "Other" : type;
            boolean filter = !java.util.List.of("Part", "Hardware").contains(type);
            partMasterLookupController.configure("parts", "Part Master", "Search " + type,
                    filter ? java.util.List.of(new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                            "Material Class", "E", material)) : java.util.List.of());
        }
        setVisible(softwareLookupGroup, software);
        setVisible(rotableLookupGroup, rotable);
        setVisible(partMasterLookupGroup, part);
        boolean serials = part || rotable;
        String selection = viewModel.serialTypeProperty().get();
        boolean range = serials && "Range".equals(selection);
        setVisible(serialTypeGroup, serials);
        setVisible(serialNumberTypeGroup, serials);
        setVisible(serialNumberGroup, serials && ("Single".equals(selection) || "Pattern".equals(selection)));
        setVisible(serialPrefixGroup, serials);
        setVisible(serialFromGroup, range);
        setVisible(serialToGroup, range);
        setVisible(rangeShiftGroup, range);
        setVisible(rangeRemarksGroup, serials);
    }

    private void configureRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        chooseRotableCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                choosePartSerialCheckBox.setSelected(false);
            } else if (!choosePartSerialCheckBox.isSelected()) {
                chooseRotableCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        choosePartSerialCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                chooseRotableCheckBox.setSelected(false);
            } else if (!chooseRotableCheckBox.isSelected()) {
                choosePartSerialCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        chooseRotableCheckBox.setSelected(true);
        applyRotableSelectionMode();
    }

    private void selectRotableModeFromCurrentModel() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null || viewModel == null) {
            return;
        }
        boolean hasRotable = hasText(viewModel.rotableIdProperty().get()) || hasText(viewModel.rotableLookupQueryProperty().get());
        boolean hasPartSerial = hasText(viewModel.partMasterIdProperty().get())
                || hasText(viewModel.partMasterLookupQueryProperty().get())
                || hasText(viewModel.serialNumberProperty().get())
                || hasText(viewModel.serialPrefixProperty().get())
                || hasText(viewModel.serialFromProperty().get())
                || hasText(viewModel.serialToProperty().get())
                || hasText(viewModel.rangeRemarksProperty().get());
        choosePartSerialCheckBox.setSelected(!hasRotable && hasPartSerial);
        chooseRotableCheckBox.setSelected(hasRotable || !hasPartSerial);
    }

    private void applyRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        boolean chooseRotable = chooseRotableCheckBox.isSelected();
        setVisible(rotableLookupGroup, chooseRotable);
        setVisible(partMasterLookupGroup, !chooseRotable);
        setVisible(serialTypeGroup, !chooseRotable);
        setVisible(serialNumberGroup, !chooseRotable);
        setVisible(serialPrefixGroup, !chooseRotable);
        setVisible(serialFromGroup, !chooseRotable);
        setVisible(serialToGroup, !chooseRotable);
        setVisible(rangeRemarksGroup, !chooseRotable);
    }

    private boolean hasText(String value) {
        return value != null && !value.isBlank();
    }

    private void setVisible(Node node, boolean visible) {
        if (node == null) {
            return;
        }
        node.setVisible(visible);
        node.setManaged(visible);
    }

    private void bind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().bindBidirectional(property);
        }
    }

    private void unbind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().unbindBidirectional(property);
        }
    }

    private void bind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().bindBidirectional(property);
        }
    }

    private void unbind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().unbindBidirectional(property);
        }
    }

    private void bind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().bindBidirectional(property);
        }
    }

    private void unbind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().unbindBidirectional(property);
        }
    }

    private void setItems(ComboBox<String> control, java.util.List<String> items) {
        if (control != null) {
            control.getItems().setAll(items);
        }
    }

    private void bindLookup(
            DbSearchLookupFieldController controller,
            String definitionKey,
            String label,
            String prompt,
            StringProperty selectedId,
            StringProperty query
    ) {
        if (controller != null) {
            LookupFieldBinding.bind(controller, definitionKey, label, prompt, selectedId, query);
        }
    }
}

Effectivity / Validity

GUI review pending

Effectivity validity dates.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Validity
Shared form reference · 2026-10-10T12:32:52.027233 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Validity
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (2)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Effective From
startDateField
CustomDateTimeField
startDateProperty
Review requiredness, validation and display.
Effective To
endDateField
CustomDateTimeField
endDateProperty
Review requiredness, validation and display.
Source evidence

Tab key: effectivity--validity
Section: Entire form
FXML SHA-256: 1027834cfe47c994b95c9ed67f43a199e064683bd7d037b94a238a913bb8f37e

coflyr-fe/src/main/resources/com/coflyr/fe/shared/effectivity/views/effectivity-validity-section.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import com.coflyr.fe.views.CustomDateTimeField?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      styleClass="standard-section-flat"
      VBox.vgrow="ALWAYS">
    <children>
        <Label styleClass="section-title" text="Validity Dates"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0"><children><Label styleClass="field-label" text="Effective From"/><CustomDateTimeField fx:id="startDateField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="0"><children><Label styleClass="field-label" text="Effective To"/><CustomDateTimeField fx:id="endDateField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.effectivity.controllers.EffectivitySectionController
package com.coflyr.fe.shared.effectivity.controllers;

import com.coflyr.fe.shared.effectivity.viewmodels.EffectivityViewModel;
import com.coflyr.fe.shared.effectivity.EffectivityEditorTypes;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.beans.value.ChangeListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.util.StringConverter;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EffectivitySectionController implements com.coflyr.fe.config.DisposableView {
    private EffectivityViewModel viewModel;
    private boolean bound;
    private boolean typeDriven;
    private ChangeListener<String> editorTypeListener;
    @FXML private Label targetTypeLabel;
    @FXML private Label editorTypeHint;
    @FXML private Label editorSectionTitle;
    private ChangeListener<String> scopeVisibilityListener;
    private ChangeListener<String> parentEffectivityListener;
    private ChangeListener<String> itemTypeListener;
    private ChangeListener<String> serialSelectionListener;
    @FXML private Node serialNumberTypeGroup;
    @FXML private Node rangeShiftGroup;
    @FXML private ComboBox<String> itemTypeComboBox;
    @FXML private Node softwareLookupGroup;

    @FXML private ToggleButton mainTabButton;
    @FXML private ToggleButton partSerialTabButton;
    @FXML private ToggleButton operatorTabButton;
    @FXML private ToggleButton validityTabButton;
    @FXML private ToggleButton remarksTabButton;
    @FXML private ToggleButton auditTabButton;
    @FXML private Node mainTabContent;
    @FXML private Node partSerialTabContent;
    @FXML private Node operatorTabContent;
    @FXML private Node validityTabContent;
    @FXML private Node remarksTabContent;
    @FXML private Node auditTabContent;
    @FXML private ComboBox<String> scopeComboBox;
    @FXML private Node parentEffectivityLookupGroup;
    @FXML private Node maintenanceTaskLookupGroup;
    @FXML private Node taskCardLookupGroup;
    @FXML private Node requirementLookupGroup;
    @FXML private Node maintenanceProgramLookupGroup;
    @FXML private Node deferredMaintenanceLookupGroup;
    @FXML private Node publicationLookupGroup;
    @FXML private Node configurationLookupGroup;
    @FXML private Node configurationMasterLookupGroup;
    @FXML private DbSearchLookupFieldController parentEffectivityLookupController;
    @FXML private TextField codeField;
    @FXML private TextField titleField;
    @FXML private ComboBox<String> relationshipTypeComboBox;
    @FXML private ComboBox<String> targetTypeComboBox;
    @FXML private ComboBox<String> nodeTypeComboBox;
    @FXML private TextField sequenceNumberField;
    @FXML private DbSearchLookupFieldController maintenanceTaskLookupController;
    @FXML private DbSearchLookupFieldController taskCardLookupController;
    @FXML private DbSearchLookupFieldController requirementLookupController;
    @FXML private DbSearchLookupFieldController maintenanceProgramMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredMaintenanceMasterLookupController;
    @FXML private DbSearchLookupFieldController deferredDefectMasterLookupController;
    @FXML private DbSearchLookupFieldController learningActivityLookupController;
    @FXML private DbSearchLookupFieldController trainingNeedAnalysisLookupController;
    @FXML private DbSearchLookupFieldController publicationMasterLookupController;
    @FXML private DbSearchLookupFieldController configurationLookupController;
    @FXML private DbSearchLookupFieldController configurationMasterLookupController;
    @FXML private ComboBox<String> serialTypeComboBox;
    @FXML private ComboBox<String> serialNumberTypeComboBox;
    @FXML private TextField serialPrefixField;
    @FXML private TextField serialFromField;
    @FXML private TextField serialToField;
    @FXML private ComboBox<String> rangeShiftComboBox;
    @FXML private Node partMasterLookupGroup;
    @FXML private Node partBomLookupGroup;
    @FXML private Node rotableLookupGroup;
    @FXML private Node rotableSelectionModeGroup;
    @FXML private CheckBox chooseRotableCheckBox;
    @FXML private CheckBox choosePartSerialCheckBox;
    @FXML private Node serialTypeGroup;
    @FXML private Node serialPrefixGroup;
    @FXML private Node serialFromGroup;
    @FXML private Node serialToGroup;
    @FXML private Node serialNumberGroup;
    @FXML private Node rangeRemarksGroup;
    @FXML private DbSearchLookupFieldController partMasterLookupController;
    @FXML private DbSearchLookupFieldController partBomLookupController;
    @FXML private DbSearchLookupFieldController rotableLookupController;
    @FXML private DbSearchLookupFieldController operationSpecificationLookupController;
    @FXML private TextField rangeRemarksField;
    @FXML private TextField serialNumberField;
    @FXML private CheckBox appliesToFlightOperationsCheckBox;
    @FXML private CheckBox appliesToGroundOperationsCheckBox;
    @FXML private CheckBox appliesToLineMaintenanceCheckBox;
    @FXML private CheckBox appliesToBaseMaintenanceCheckBox;
    @FXML private CheckBox appliesToComponentMaintenanceCheckBox;
    @FXML private TextField startDateField;
    @FXML private TextField endDateField;

    public EffectivitySectionController(EffectivityViewModel viewModel) {
        this.viewModel = viewModel;
    }

    public void setTypeDriven(boolean typeDriven) {
        this.typeDriven = typeDriven;
    }

    @Override public void disposeView() {
        if (bound) unbindCurrentViewModel();
        bound = false;
    }

    @FXML
    private void initialize() {
        if (mainTabButton != null) {
            configureTabs();
        }
        configureRotableSelectionMode();
    }

    public void bindTo(EffectivityViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        if (bound && viewModel == this.viewModel) {
            return;
        }
        if (bound) unbindCurrentViewModel();
        this.viewModel = viewModel;
        bound = true;

        setItems(scopeComboBox, viewModel.scopes());
        setItems(relationshipTypeComboBox, viewModel.relationshipTypes());
        setItems(targetTypeComboBox, viewModel.targetTypes());
        if (typeDriven && targetTypeComboBox != null) {
            setItems(targetTypeComboBox, EffectivityEditorTypes.TYPES);
            targetTypeComboBox.setConverter(new StringConverter<>() {
                @Override public String toString(String value) { return EffectivityEditorTypes.label(value); }
                @Override public String fromString(String label) {
                    return EffectivityEditorTypes.TYPES.stream()
                            .filter(type -> EffectivityEditorTypes.label(type).equals(label))
                            .findFirst().orElse(label);
                }
            });
        }
        setItems(nodeTypeComboBox, viewModel.nodeTypes());
        setItems(serialTypeComboBox, viewModel.serialTypes());
        setItems(serialNumberTypeComboBox, viewModel.serialNumberTypes());
        setItems(rangeShiftComboBox, viewModel.rangeShifts());

        bind(scopeComboBox, viewModel.scopeProperty());
        if (scopeComboBox != null) scopeComboBox.setDisable(true);
        bind(codeField, viewModel.codeProperty());
        bindLookup(deferredDefectMasterLookupController, "deferred-defects", "Deferred Defect",
                "Search deferred defect", viewModel.deferredDefectMasterIdProperty(), viewModel.deferredDefectLookupQueryProperty());
        bindLookup(learningActivityLookupController, "learningactivity", "Learning Activity",
                "Search learning activity", viewModel.learningActivityIdProperty(), viewModel.learningActivityLookupQueryProperty());
        bindLookup(trainingNeedAnalysisLookupController, "trainingneedanalysis", "Training Need Analysis",
                "Search training need analysis", viewModel.trainingNeedAnalysisIdProperty(), viewModel.trainingNeedAnalysisLookupQueryProperty());
        bind(titleField, viewModel.titleProperty());
        bindLookup(parentEffectivityLookupController, "effectivity", "Existing Effectivity", "Search existing effectivity",
                viewModel.parentEffectivityIdProperty(), viewModel.parentEffectivityLookupQueryProperty());
        parentEffectivityListener = (observable, oldValue, newValue) -> {
            if (newValue != null && !newValue.isBlank()) {
                viewModel.nodeTypeProperty().set("LinkedObject");
            }
        };
        viewModel.parentEffectivityIdProperty().addListener(parentEffectivityListener);
        if (!viewModel.parentEffectivityIdProperty().get().isBlank()) {
            viewModel.nodeTypeProperty().set("LinkedObject");
        }
        bind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        bind(targetTypeComboBox, viewModel.targetTypeProperty());
        if (itemTypeComboBox != null) {
            setItems(itemTypeComboBox, java.util.List.of("Rotable", "Aircraft", "Component", "Part", "Hardware", "Consumable",
                    "Expendable", "Repairable", "Tool", "RawMaterial", "OtherMaterial", "Software"));
            bind(itemTypeComboBox, viewModel.targetTypeProperty());
            itemTypeListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.targetTypeProperty().addListener(itemTypeListener);
            applyItemTypeVisibility();
        }
        bind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        bind(sequenceNumberField, viewModel.sequenceNumberProperty());
        bindLookup(maintenanceTaskLookupController, "maintenance-tasks", "Maintenance Task", "Search maintenance task",
                viewModel.maintenanceTaskIdProperty(), viewModel.maintenanceTaskLookupQueryProperty());
        bindLookup(taskCardLookupController, "task-cards", "Task Card", "Search task card",
                viewModel.taskCardIdProperty(), viewModel.taskCardLookupQueryProperty());
        bindLookup(requirementLookupController, "requirements", "Requirement", "Search requirement",
                viewModel.requirementIdProperty(), viewModel.requirementLookupQueryProperty());
        bindLookup(maintenanceProgramMasterLookupController, "maintenance-programs", "Maintenance Program",
                "Search maintenance program", viewModel.maintenanceProgramMasterIdProperty(),
                viewModel.maintenanceProgramMasterLookupQueryProperty());
        bindLookup(deferredMaintenanceMasterLookupController, "deferred-maintenance", "Deferred Maintenance",
                "Search deferred maintenance", viewModel.deferredMaintenanceMasterIdProperty(),
                viewModel.deferredMaintenanceMasterLookupQueryProperty());
        bindLookup(publicationMasterLookupController, "publications", "Publication", "Search publication",
                viewModel.publicationMasterIdProperty(), viewModel.publicationMasterLookupQueryProperty());
        bindLookup(configurationLookupController, "configurations", "Configuration", "Search configuration",
                viewModel.configurationIdProperty(), viewModel.configurationLookupQueryProperty());
        bindLookup(configurationMasterLookupController, "configuration-masters", "Configuration Master",
                "Search configuration master", viewModel.configurationMasterIdProperty(),
                viewModel.configurationMasterLookupQueryProperty());
        bind(serialTypeComboBox, viewModel.serialTypeProperty());
        if (itemTypeComboBox != null) {
            serialSelectionListener = (observable, oldValue, newValue) -> applyItemTypeVisibility();
            viewModel.serialTypeProperty().addListener(serialSelectionListener);
        }
        bind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        bind(serialPrefixField, viewModel.serialPrefixProperty());
        bind(serialFromField, viewModel.serialFromProperty());
        bind(serialToField, viewModel.serialToProperty());
        bind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        bindLookup(partMasterLookupController, "parts", "Part Master", "Search part master",
                viewModel.partMasterIdProperty(), viewModel.partMasterLookupQueryProperty());
        bindLookup(partBomLookupController, "part-boms", "Part BOM", "Search part BOM",
                viewModel.partBomIdProperty(), viewModel.partBomLookupQueryProperty());
        bindLookup(rotableLookupController, "rotables", "Rotable", "Search rotable",
                viewModel.rotableIdProperty(), viewModel.rotableLookupQueryProperty());
        bindLookup(operationSpecificationLookupController, "operation-specifications", "Operations",
                "Search operations", viewModel.operationSpecificationIdProperty(),
                viewModel.operationSpecificationLookupQueryProperty());
        bind(rangeRemarksField, viewModel.rangeRemarksProperty());
        bind(serialNumberField, viewModel.serialNumberProperty());
        bind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        bind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        bind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        bind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        bind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        bind(startDateField, viewModel.startDateProperty());
        bind(endDateField, viewModel.endDateProperty());
        scopeVisibilityListener = (observable, oldScope, newScope) -> applyVisibility();
        viewModel.scopeProperty().addListener(scopeVisibilityListener);
        selectRotableModeFromCurrentModel();
        applyVisibility();
        if (typeDriven) {
            editorTypeListener = (observable, oldType, newType) -> applyEditorType();
            viewModel.targetTypeProperty().addListener(editorTypeListener);
            applyEditorType();
        }
    }

    private void applyEditorType() {
        String type = viewModel.targetTypeProperty().get();
        if (targetTypeLabel != null) targetTypeLabel.setText("Effectivity Type");
        if (targetTypeComboBox != null && type != null && !type.isBlank()
                && !targetTypeComboBox.getItems().contains(type)) targetTypeComboBox.getItems().add(type);
        if (editorTypeHint != null) {
            setVisible(editorTypeHint, true);
            editorTypeHint.setText("main".equals(EffectivityEditorTypes.tabFor(type))
                    ? "Choose the subject of this effectivity. Its matching editor opens automatically. Business Partner restrictions are maintained in Links."
                    : EffectivityEditorTypes.label(type) + " Effectivity is ready in the matching tab. Validity, Remarks, Audit and Links remain shared. Switching types preserves entered values; review existing criteria before saving.");
        }
        if (editorSectionTitle != null) editorSectionTitle.setText(EffectivityEditorTypes.label(type) + " Effectivity");
        setVisible(rotableSelectionModeGroup, false);
    }

    @FXML private void onMainTabClicked() { showTab(mainTabContent); }
    @FXML private void onPartSerialTabClicked() { showTab(partSerialTabContent); }
    @FXML private void onOperatorTabClicked() { showTab(operatorTabContent); }
    @FXML private void onValidityTabClicked() { showTab(validityTabContent); }
    @FXML private void onRemarksTabClicked() { showTab(remarksTabContent); }
    @FXML private void onAuditTabClicked() { showTab(auditTabContent); }

    private void configureTabs() {
        ToggleGroup group = new ToggleGroup();
        for (ToggleButton button : new ToggleButton[]{mainTabButton, partSerialTabButton, operatorTabButton, validityTabButton, remarksTabButton, auditTabButton}) {
            if (button != null) {
                button.setToggleGroup(group);
            }
        }
        group.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> {
            if (newToggle == null && oldToggle != null) {
                oldToggle.setSelected(true);
            }
        });
        mainTabButton.setSelected(true);
        showTab(mainTabContent);
    }

    private void showTab(Node selectedContent) {
        for (Node content : new Node[]{mainTabContent, partSerialTabContent, operatorTabContent, validityTabContent, remarksTabContent, auditTabContent}) {
            if (content == null) {
                continue;
            }
            boolean selected = content == selectedContent;
            content.setManaged(selected);
            content.setVisible(selected);
        }
    }

    private void unbindCurrentViewModel() {
        if (editorTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(editorTypeListener);
            editorTypeListener = null;
        }
        if (serialSelectionListener != null) {
            viewModel.serialTypeProperty().removeListener(serialSelectionListener);
            serialSelectionListener = null;
        }
        if (itemTypeListener != null) {
            viewModel.targetTypeProperty().removeListener(itemTypeListener);
            itemTypeListener = null;
        }
        unbind(itemTypeComboBox, viewModel.targetTypeProperty());
        if (scopeVisibilityListener != null) {
            viewModel.scopeProperty().removeListener(scopeVisibilityListener);
            scopeVisibilityListener = null;
        }
        if (parentEffectivityListener != null) {
            viewModel.parentEffectivityIdProperty().removeListener(parentEffectivityListener);
            parentEffectivityListener = null;
        }
        unbind(scopeComboBox, viewModel.scopeProperty());
        unbind(codeField, viewModel.codeProperty());
        unbind(titleField, viewModel.titleProperty());
        unbind(relationshipTypeComboBox, viewModel.relationshipTypeProperty());
        unbind(targetTypeComboBox, viewModel.targetTypeProperty());
        unbind(nodeTypeComboBox, viewModel.nodeTypeProperty());
        unbind(sequenceNumberField, viewModel.sequenceNumberProperty());
        unbind(serialTypeComboBox, viewModel.serialTypeProperty());
        unbind(serialNumberTypeComboBox, viewModel.serialNumberTypeProperty());
        unbind(serialPrefixField, viewModel.serialPrefixProperty());
        unbind(serialFromField, viewModel.serialFromProperty());
        unbind(serialToField, viewModel.serialToProperty());
        unbind(rangeShiftComboBox, viewModel.rangeShiftProperty());
        unbind(rangeRemarksField, viewModel.rangeRemarksProperty());
        unbind(serialNumberField, viewModel.serialNumberProperty());
        unbind(appliesToFlightOperationsCheckBox, viewModel.appliesToFlightOperationsProperty());
        unbind(appliesToGroundOperationsCheckBox, viewModel.appliesToGroundOperationsProperty());
        unbind(appliesToLineMaintenanceCheckBox, viewModel.appliesToLineMaintenanceProperty());
        unbind(appliesToBaseMaintenanceCheckBox, viewModel.appliesToBaseMaintenanceProperty());
        unbind(appliesToComponentMaintenanceCheckBox, viewModel.appliesToComponentMaintenanceProperty());
        unbind(startDateField, viewModel.startDateProperty());
        unbind(endDateField, viewModel.endDateProperty());
    }

    private void applyVisibility() {
        for (Node node : new Node[]{
                parentEffectivityLookupGroup, maintenanceTaskLookupGroup, taskCardLookupGroup,
                requirementLookupGroup, maintenanceProgramLookupGroup, deferredMaintenanceLookupGroup,
                publicationLookupGroup, configurationLookupGroup, configurationMasterLookupGroup,
                partMasterLookupGroup, partBomLookupGroup, rotableLookupGroup,
                rotableSelectionModeGroup, serialTypeGroup, serialPrefixGroup, serialFromGroup, serialToGroup,
                serialNumberGroup, rangeRemarksGroup
        }) {
            setVisible(node, true);
        }
        applyRotableSelectionMode();
        applyItemTypeVisibility();
        if (typeDriven) applyEditorType();
    }

    private void applyItemTypeVisibility() {
        if (itemTypeComboBox == null) return;
        String type = viewModel.targetTypeProperty().get();
        boolean software = "Software".equals(type);
        boolean rotable = java.util.List.of("Rotable", "Aircraft", "Component").contains(type == null ? "" : type);
        boolean part = itemTypeComboBox.getItems().contains(type) && !software && !rotable;
        if (software && configurationLookupController != null) {
            configurationLookupController.configure("configurations", "Software Configuration",
                    "Search software configuration", java.util.List.of(
                            new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                                    "Functional Position Type", "E", "Software")));
        }
        if (part && partMasterLookupController != null) {
            String material = "OtherMaterial".equals(type) ? "Other" : type;
            boolean filter = !java.util.List.of("Part", "Hardware").contains(type);
            partMasterLookupController.configure("parts", "Part Master", "Search " + type,
                    filter ? java.util.List.of(new com.coflyr.fe.services.DbSearchService.DbSearchCriterion(
                            "Material Class", "E", material)) : java.util.List.of());
        }
        setVisible(softwareLookupGroup, software);
        setVisible(rotableLookupGroup, rotable);
        setVisible(partMasterLookupGroup, part);
        boolean serials = part || rotable;
        String selection = viewModel.serialTypeProperty().get();
        boolean range = serials && "Range".equals(selection);
        setVisible(serialTypeGroup, serials);
        setVisible(serialNumberTypeGroup, serials);
        setVisible(serialNumberGroup, serials && ("Single".equals(selection) || "Pattern".equals(selection)));
        setVisible(serialPrefixGroup, serials);
        setVisible(serialFromGroup, range);
        setVisible(serialToGroup, range);
        setVisible(rangeShiftGroup, range);
        setVisible(rangeRemarksGroup, serials);
    }

    private void configureRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        chooseRotableCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                choosePartSerialCheckBox.setSelected(false);
            } else if (!choosePartSerialCheckBox.isSelected()) {
                chooseRotableCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        choosePartSerialCheckBox.selectedProperty().addListener((observable, oldValue, selected) -> {
            if (selected) {
                chooseRotableCheckBox.setSelected(false);
            } else if (!chooseRotableCheckBox.isSelected()) {
                choosePartSerialCheckBox.setSelected(true);
                return;
            }
            applyRotableSelectionMode();
        });
        chooseRotableCheckBox.setSelected(true);
        applyRotableSelectionMode();
    }

    private void selectRotableModeFromCurrentModel() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null || viewModel == null) {
            return;
        }
        boolean hasRotable = hasText(viewModel.rotableIdProperty().get()) || hasText(viewModel.rotableLookupQueryProperty().get());
        boolean hasPartSerial = hasText(viewModel.partMasterIdProperty().get())
                || hasText(viewModel.partMasterLookupQueryProperty().get())
                || hasText(viewModel.serialNumberProperty().get())
                || hasText(viewModel.serialPrefixProperty().get())
                || hasText(viewModel.serialFromProperty().get())
                || hasText(viewModel.serialToProperty().get())
                || hasText(viewModel.rangeRemarksProperty().get());
        choosePartSerialCheckBox.setSelected(!hasRotable && hasPartSerial);
        chooseRotableCheckBox.setSelected(hasRotable || !hasPartSerial);
    }

    private void applyRotableSelectionMode() {
        if (chooseRotableCheckBox == null || choosePartSerialCheckBox == null) {
            return;
        }
        boolean chooseRotable = chooseRotableCheckBox.isSelected();
        setVisible(rotableLookupGroup, chooseRotable);
        setVisible(partMasterLookupGroup, !chooseRotable);
        setVisible(serialTypeGroup, !chooseRotable);
        setVisible(serialNumberGroup, !chooseRotable);
        setVisible(serialPrefixGroup, !chooseRotable);
        setVisible(serialFromGroup, !chooseRotable);
        setVisible(serialToGroup, !chooseRotable);
        setVisible(rangeRemarksGroup, !chooseRotable);
    }

    private boolean hasText(String value) {
        return value != null && !value.isBlank();
    }

    private void setVisible(Node node, boolean visible) {
        if (node == null) {
            return;
        }
        node.setVisible(visible);
        node.setManaged(visible);
    }

    private void bind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().bindBidirectional(property);
        }
    }

    private void unbind(TextInputControl control, StringProperty property) {
        if (control != null) {
            control.textProperty().unbindBidirectional(property);
        }
    }

    private void bind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().bindBidirectional(property);
        }
    }

    private void unbind(ComboBox<String> control, StringProperty property) {
        if (control != null) {
            control.valueProperty().unbindBidirectional(property);
        }
    }

    private void bind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().bindBidirectional(property);
        }
    }

    private void unbind(CheckBox control, BooleanProperty property) {
        if (control != null) {
            control.selectedProperty().unbindBidirectional(property);
        }
    }

    private void setItems(ComboBox<String> control, java.util.List<String> items) {
        if (control != null) {
            control.getItems().setAll(items);
        }
    }

    private void bindLookup(
            DbSearchLookupFieldController controller,
            String definitionKey,
            String label,
            String prompt,
            StringProperty selectedId,
            StringProperty query
    ) {
        if (controller != null) {
            LookupFieldBinding.bind(controller, definitionKey, label, prompt, selectedId, query);
        }
    }
}

Effectivity / Remarks

GUI review pending

Effectivity remarks.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Remarks
Shared form reference · 2026-10-10T12:32:55.474097 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Remarks
Static JavaFX layout · 1280 × 284 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (3)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Description
descriptionField
TextField
descriptionProperty
promptText=Description
Details
detailsField
TextField
detailsProperty
promptText=Details
General Remarks
remarksField
TextField
remarksProperty
promptText=General remarks
Source evidence

Tab key: effectivity--remarks
Section: Entire form
FXML SHA-256: b9938281a3c8cda2d4d5f402e9b346f99202fe71e9fe5fe2ae56d2077ce08254

coflyr-fe/src/main/resources/com/coflyr/fe/shared/remarks/views/remarks-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.remarks.controllers.RemarksSectionController"
      fillWidth="true"
      maxWidth="Infinity"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Universal Remarks"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Description"/>
                        <TextField fx:id="descriptionField" maxWidth="Infinity" prefWidth="1.0" promptText="Description" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                    <children>
                        <Label styleClass="field-label" text="Details"/>
                        <TextField fx:id="detailsField" maxWidth="Infinity" prefWidth="1.0" promptText="Details" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2">
                    <children>
                        <Label styleClass="field-label" text="General Remarks"/>
                        <TextField fx:id="remarksField" maxWidth="Infinity" prefWidth="1.0" promptText="General remarks" styleClass="text-input"/>
                    </children>
                </VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="100.0"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.remarks.controllers.RemarksSectionController
package com.coflyr.fe.shared.remarks.controllers;

import com.coflyr.fe.config.DisposableView;
import com.coflyr.fe.shared.remarks.viewmodels.RemarksViewModel;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RemarksSectionController implements DisposableView {
    private RemarksViewModel viewModel;

    @FXML private TextField descriptionField;
    @FXML private TextField detailsField;
    @FXML private TextField remarksField;

    @FXML
    private void initialize() {
        bindTo(viewModel);
    }

    public void bindTo(RemarksViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        unbindCurrentViewModel();
        this.viewModel = viewModel;
        descriptionField.textProperty().bindBidirectional(viewModel.descriptionProperty());
        detailsField.textProperty().bindBidirectional(viewModel.detailsProperty());
        remarksField.textProperty().bindBidirectional(viewModel.remarksProperty());
    }

    private void unbindCurrentViewModel() {
        if (viewModel == null || descriptionField == null || detailsField == null || remarksField == null) {
            return;
        }
        descriptionField.textProperty().unbindBidirectional(viewModel.descriptionProperty());
        detailsField.textProperty().unbindBidirectional(viewModel.detailsProperty());
        remarksField.textProperty().unbindBidirectional(viewModel.remarksProperty());
    }

    @Override
    public void disposeView() {
        unbindCurrentViewModel();
        viewModel = null;
    }
}

Effectivity / Audit

GUI review pending

Effectivity audit details.

Shared form reference — Same shared FXML and controller, captured in Configuration → Effectivity. The exact owning-workspace path has not been captured; verify owner context with a saved record.

Shared form reference: Effectivity / Audit
Shared form reference · 2026-10-10T12:33:00.020051 · Click for full size.
Full source layout (controllers disabled)

Current FXML + application CSS. Conditional controls remain visible as declared. Generic lookup labels may read Owner until initialized by a controller; see the control inventory for intended labels. Custom lookup adapters use a label/search input rendering.

Static JavaFX source layout: Effectivity / Audit
Static JavaFX layout · 1280 × 490 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (12)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Business Status
businessStatusComboBox
ComboBox
businessStatusProperty
Review requiredness, validation and display.
Approval Status
approvalStatusComboBox
ComboBox
approvalStatusProperty
Review requiredness, validation and display.
valid From
validFromField
Date/time input
Controller-defined
Review timezone, format, requiredness and read-only states.
valid To
validToField
Date/time input
Controller-defined
Review timezone, format, requiredness and read-only states.
audit Date
auditDateField
Date/time input
Controller-defined
Review timezone, format, requiredness and read-only states.
effective Date
effectiveDateField
Date/time input
Controller-defined
Review timezone, format, requiredness and read-only states.
audit Activation Date
auditActivationDateField
Date/time input
Controller-defined
Review timezone, format, requiredness and read-only states.
Audit Remarks
auditRemarksField
TextField
auditRemarksProperty
promptText=Audit remarks
Pre Approved
preApprovedCheckBox
CheckBox
preApprovedProperty
Review requiredness, validation and display.
Sent To User
sentToUserLookup
UUID search lookup
sentToUserIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Sent To Department
sentToDepartmentLookup
UUID search lookup
sentToDepartmentIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Status Information
statusInformationField
TextArea
statusInformationProperty
editable=false; promptText=Maintained by the workflow
Source evidence

Tab key: effectivity--audit
Section: Entire form
FXML SHA-256: 90712c80993bb2afd680a31fb3fac9ef1511dd5eecb67b10d3d245f5077ddfbb

coflyr-fe/src/main/resources/com/coflyr/fe/shared/audit/views/audit-min-section.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.shared.audit.controllers.AuditMinSectionController"
      fillWidth="true"
      maxWidth="Infinity"
      spacing="7"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="System Audit"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Business Status"/>
                        <ComboBox fx:id="businessStatusComboBox" maxWidth="Infinity" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="1" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0">
                    <children>
                        <Label styleClass="field-label" text="Approval Status"/>
                        <ComboBox fx:id="approvalStatusComboBox" maxWidth="Infinity" styleClass="text-input"/>
                    </children>
                </VBox>
                <fx:include fx:id="validFromField" source="../../../views/controls/datetime-field.fxml" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
                <fx:include fx:id="validToField" source="../../../views/controls/datetime-field.fxml" GridPane.columnIndex="1" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
                <fx:include fx:id="auditDateField" source="../../../views/controls/datetime-field.fxml" GridPane.columnIndex="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="0"/>
                <fx:include fx:id="effectiveDateField" source="../../../views/controls/datetime-field.fxml" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2"/>
                <fx:include fx:id="auditActivationDateField" source="../../../views/controls/datetime-field.fxml" GridPane.columnIndex="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3">
                    <children>
                        <Label styleClass="field-label" text="Audit Remarks"/>
                        <TextField fx:id="auditRemarksField" maxWidth="Infinity" prefWidth="1.0" promptText="Audit remarks" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group" GridPane.columnIndex="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3">
                    <children>
                        <Label styleClass="field-label" text="Pre Approval"/>
                        <CheckBox fx:id="preApprovedCheckBox" focusTraversable="false" styleClass="form-check-box" text="Pre Approved"/>
                    </children>
                </VBox>
                <fx:include fx:id="sentToUserLookup" source="../../../views/controls/db-search-lookup-field.fxml" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="4"/>
                <fx:include fx:id="sentToDepartmentLookup" source="../../../views/controls/db-search-lookup-field.fxml" GridPane.columnIndex="1" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="4"/>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="5"><Label styleClass="field-label" text="Status Information"/><TextArea fx:id="statusInformationField" editable="false" maxWidth="Infinity" prefRowCount="2" wrapText="true" styleClass="form-text-area" promptText="Maintained by the workflow"/></VBox>
            </children>
            <columnConstraints>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.333"/>
                <ColumnConstraints fillWidth="true" hgrow="ALWAYS" percentWidth="33.334"/>
            </columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.shared.audit.controllers.AuditMinSectionController
package com.coflyr.fe.shared.audit.controllers;

import com.coflyr.fe.config.DisposableView;
import com.coflyr.fe.services.DateTimeParserService;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.shared.audit.viewmodels.AuditViewModel;
import com.coflyr.fe.shared.audit.viewmodels.EffectiveDateAuditViewModel;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import com.coflyr.fe.views.DateTimeFieldController;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeParseException;

/**
 * The Pre Approved checkbox is optional here (fx:id may be absent, e.g. the
 * Training Gap audit view - Pre Approved lives on its main Gap tab instead).
 * The apply-defaults-when-checked behavior is sourced from the ViewModel's
 * own preApprovedProperty, not the local checkbox, so it still works
 * correctly when Pre Approved is toggled from a different tab entirely.
 * Fields here are never disabled based on Pre Approved - viewing/editing the
 * audit state should not be locked out just because the record happens to
 * be pre-approved.
 */
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AuditMinSectionController implements DisposableView {
    private AuditViewModel viewModel;
    private final DateTimeParserService dateTimeParserService;
    private ChangeListener<String> auditDateListener;
    private ChangeListener<String> validFromListener;
    private ChangeListener<String> validToListener;
    private ChangeListener<String> effectiveDateListener;
    private ChangeListener<LocalDateTime> auditActivationDateListener;
    private ChangeListener<Boolean> preApprovedListener;

    @FXML private ComboBox<String> businessStatusComboBox;
    @FXML private ComboBox<String> approvalStatusComboBox;
    @FXML private Node auditDateField;
    @FXML private DateTimeFieldController auditDateFieldController;
    @FXML private DateTimeFieldController validFromFieldController;
    @FXML private DateTimeFieldController validToFieldController;
    @FXML private Node effectiveDateField;
    @FXML private DateTimeFieldController effectiveDateFieldController;
    @FXML private CheckBox preApprovedCheckBox;
    @FXML private TextField auditRemarksField;
    @FXML private javafx.scene.control.TextArea statusInformationField;
    @FXML private DateTimeFieldController auditActivationDateFieldController;
    @FXML private DbSearchLookupFieldController sentToUserLookupController;
    @FXML private DbSearchLookupFieldController sentToDepartmentLookupController;

    public AuditMinSectionController(DateTimeParserService dateTimeParserService) {
        this.dateTimeParserService = dateTimeParserService;
    }

    @FXML
    private void initialize() {
        auditDateFieldController.configure("Audit Date", "Audit date and time", "HH:mm");
        auditDateFieldController.selectedDateTimeProperty().addListener((observable, oldValue, newValue) -> updateAuditDateModel(newValue));
        validFromFieldController.configure("Valid From", "Start of validity", "HH:mm");
        validFromFieldController.selectedDateTimeProperty().addListener((observable, oldValue, newValue) -> updateDateTimeModel(viewModel == null ? null : viewModel.validFromProperty(), newValue));
        validToFieldController.configure("Valid To", "End of validity", "HH:mm");
        validToFieldController.selectedDateTimeProperty().addListener((observable, oldValue, newValue) -> updateDateTimeModel(viewModel == null ? null : viewModel.validToProperty(), newValue));
        effectiveDateFieldController.configure("Effective Date", "Date the record becomes effective", "HH:mm");
        auditActivationDateFieldController.configure("Activation Date", "File activation date", "HH:mm");
        effectiveDateFieldController.selectedDateTimeProperty().addListener((observable, oldValue, newValue) -> {
            if (viewModel instanceof EffectiveDateAuditViewModel effectiveDateModel) {
                updateDateTimeModel(effectiveDateModel.effectiveDateProperty(), newValue);
            }
        });
    }

    public void bindTo(AuditViewModel viewModel) {
        if (viewModel == null) {
            return;
        }
        unbindCurrentViewModel();
        this.viewModel = viewModel;
        if (statusInformationField != null) {
            statusInformationField.getParent().setVisible(viewModel.extendedRevisionControlsAvailableProperty().get());
            statusInformationField.getParent().setManaged(viewModel.extendedRevisionControlsAvailableProperty().get());
        }
        businessStatusComboBox.setItems(viewModel.businessStatuses());
        approvalStatusComboBox.setItems(viewModel.approvalStatuses());
        businessStatusComboBox.valueProperty().bindBidirectional(viewModel.businessStatusProperty());
        approvalStatusComboBox.valueProperty().bindBidirectional(viewModel.approvalStatusProperty());
        updateAuditDateControl(viewModel.auditDateProperty().get());
        auditDateListener = (observable, oldValue, newValue) -> updateAuditDateControl(newValue);
        viewModel.auditDateProperty().addListener(auditDateListener);
        updateDateTimeControl(validFromFieldController, viewModel.validFromProperty().get());
        validFromListener = (observable, oldValue, newValue) -> updateDateTimeControl(validFromFieldController, newValue);
        viewModel.validFromProperty().addListener(validFromListener);
        updateDateTimeControl(validToFieldController, viewModel.validToProperty().get());
        validToListener = (observable, oldValue, newValue) -> updateDateTimeControl(validToFieldController, newValue);
        viewModel.validToProperty().addListener(validToListener);
        boolean supportsEffectiveDate = viewModel instanceof EffectiveDateAuditViewModel;
        effectiveDateField.setVisible(supportsEffectiveDate);
        effectiveDateField.setManaged(supportsEffectiveDate);
        if (supportsEffectiveDate) {
            EffectiveDateAuditViewModel effectiveDateModel = (EffectiveDateAuditViewModel) viewModel;
            updateDateTimeControl(effectiveDateFieldController, effectiveDateModel.effectiveDateProperty().get());
            effectiveDateListener = (observable, oldValue, newValue) ->
                    updateDateTimeControl(effectiveDateFieldController, newValue);
            effectiveDateModel.effectiveDateProperty().addListener(effectiveDateListener);
        }
        if (preApprovedCheckBox != null) {
            preApprovedCheckBox.selectedProperty().bindBidirectional(viewModel.preApprovedProperty());
        }
        preApprovedListener = (observable, oldValue, newValue) -> {
            if (newValue) {
                applyPreApprovedSelections();
            }
            freezePreApprovedSelections(newValue);
        };
        viewModel.preApprovedProperty().addListener(preApprovedListener);
        // Apply on bind as well as on change, so a record that loads already
        // pre-approved opens with its selections frozen rather than editable
        // until the box is next toggled.
        freezePreApprovedSelections(viewModel.preApprovedProperty().get());
        auditRemarksField.textProperty().bindBidirectional(viewModel.auditRemarksProperty());
        if (statusInformationField != null) statusInformationField.textProperty().bind(viewModel.statusInformationProperty());
        updateDateTimeControl(auditActivationDateFieldController, viewModel.auditActivationDateProperty().get());
        auditActivationDateListener = (observable, oldValue, newValue) -> updateDateTimeModel(viewModel == null ? null : viewModel.auditActivationDateProperty(), newValue);
        auditActivationDateFieldController.selectedDateTimeProperty().addListener(auditActivationDateListener);
        LookupFieldBinding.bind(sentToUserLookupController, "applicationusers", "Sent To User",
                "Search user", viewModel.sentToUserIdProperty(), viewModel.sentToUserDisplayProperty());
        LookupFieldBinding.bind(sentToDepartmentLookupController, "department", "Sent To Department",
                "Search department", viewModel.sentToDepartmentIdProperty(), viewModel.sentToDepartmentDisplayProperty());
        if (viewModel.preApprovedProperty().get()) {
            applyPreApprovedSelections();
        }
    }

    private void unbindCurrentViewModel() {
        if (viewModel == null) {
            return;
        }
        businessStatusComboBox.valueProperty().unbindBidirectional(viewModel.businessStatusProperty());
        approvalStatusComboBox.valueProperty().unbindBidirectional(viewModel.approvalStatusProperty());
        if (auditDateListener != null) {
            viewModel.auditDateProperty().removeListener(auditDateListener);
            auditDateListener = null;
        }
        if (validFromListener != null) {
            viewModel.validFromProperty().removeListener(validFromListener);
            validFromListener = null;
        }
        if (validToListener != null) {
            viewModel.validToProperty().removeListener(validToListener);
            validToListener = null;
        }
        if (effectiveDateListener != null && viewModel instanceof EffectiveDateAuditViewModel effectiveDateModel) {
            effectiveDateModel.effectiveDateProperty().removeListener(effectiveDateListener);
            effectiveDateListener = null;
        }
        if (preApprovedCheckBox != null) {
            preApprovedCheckBox.selectedProperty().unbindBidirectional(viewModel.preApprovedProperty());
        }
        if (preApprovedListener != null) {
            viewModel.preApprovedProperty().removeListener(preApprovedListener);
            preApprovedListener = null;
        }
        auditRemarksField.textProperty().unbindBidirectional(viewModel.auditRemarksProperty());
        if (statusInformationField != null) statusInformationField.textProperty().unbind();
        // The form is reused across records, so hand the fields back enabled -
        // otherwise a pre-approved record would leave the next one frozen.
        freezePreApprovedSelections(false);
    }

    private void applyPreApprovedSelections() {
        // Rendering or reopening Audit must not activate an explicitly inactive record.
        // The view model handles defaults when the user actually ticks Pre Approved.
        viewModel.approvalStatusProperty().set("Released");
        approvalStatusComboBox.getSelectionModel().select("Released");
    }

    /**
     * Locks the selections pre-approval decides, so the values that were filled
     * in cannot then be cleared or changed to something that contradicts the
     * ticked box. Unticking hands them back.
     *
     * <p>Disabled rather than made read-only because a JavaFX ComboBox has no
     * read-only mode: it is either editable or not, and a non-editable combo
     * still opens its list and accepts a different choice.</p>
     */
    private void freezePreApprovedSelections(boolean frozen) {
        if (businessStatusComboBox != null) {
            businessStatusComboBox.setDisable(frozen);
        }
        if (approvalStatusComboBox != null) {
            approvalStatusComboBox.setDisable(frozen);
        }
        if (auditDateField != null) {
            auditDateField.setDisable(frozen);
        }
    }

    private void updateAuditDateControl(String value) {
        try {
            auditDateFieldController.setSelectedDateTime(dateTimeParserService.parseDateTime(value));
        } catch (DateTimeParseException ignored) {
            auditDateFieldController.clearSelection();
        }
    }

    private void updateAuditDateModel(LocalDateTime value) {
        String formattedValue = dateTimeParserService.formatDateTime(value);
        if (!formattedValue.equals(viewModel.auditDateProperty().get())) {
            viewModel.auditDateProperty().set(formattedValue);
        }
    }

    private void updateDateTimeControl(DateTimeFieldController controller, String value) {
        try {
            controller.setSelectedDateTime(dateTimeParserService.parseDateTime(value));
        } catch (DateTimeParseException ignored) {
            controller.clearSelection();
        }
    }

    private void updateDateTimeModel(javafx.beans.property.StringProperty property, LocalDateTime value) {
        if (property == null) {
            return;
        }
        String formattedValue = dateTimeParserService.formatDateTime(value);
        if (!formattedValue.equals(property.get())) {
            property.set(formattedValue);
        }
    }

    @Override
    public void disposeView() {
        unbindCurrentViewModel();
        viewModel = null;
    }
}

Backend-to-GUI coverage

Fields from the reviewed backend catalog. Matches below use explicit controller/view-model property names and generic control IDs. Unmatched fields may be server-managed, derived, linked or absent from this form. Confirm each choice before implementation.

Reviewed BE fieldType / storageGUI evidenceGUI decision
Id
Stable identifier for this record.
Guid · RequiredNo direct binding match — review mapping or deliberate omission
ConfigurationName
Records configuration name for this configuration master.
string · RequiredProfile / Configuration Name *
DefaultEffectivityMainPartOnly
Indicates whether default effectivity main part only applies to this configuration master.
bool · RequiredDefaults / Effectivity Main Part Only
ImportConsumables
Indicates whether import consumables applies to this configuration master.
bool · RequiredDefaults / Import Consumables
IncludeAlternates
Indicates whether include alternates applies to this configuration master.
bool · RequiredDefaults / Include Alternates
ConfigurationDescription
Records configuration description for this configuration master.
string · NullableNo direct binding match — review mapping or deliberate omission
SystemAuditId
Links to system audit history when this model stores the reference.
Guid? · NullableNo direct binding match — review mapping or deliberate omission
AlternateMode
Records alternate mode for this configuration master.
int · RequiredProfile / Alternate Mode
CheckerScope
Records checker scope for this configuration master.
string · RequiredDefaults / Checker Scope
ConfigurationMasterCode
Records configuration master code for this configuration master.
string · RequiredProfile / Configuration Code *
ConfigurationType
Records configuration type for this configuration master.
int · RequiredProfile / Configuration Type
DefaultFpGeneratorMode
Records default fp generator mode for this configuration master.
int? · NullableProfile / FP Generator Mode
DefaultSubConfigurationMode
Records default sub configuration mode for this configuration master.
int · RequiredDefaults / Sub-configuration Mode
ImportedSpecification
Records imported specification for this configuration master.
string · NullableDefaults / Imported Specification
Source
Records source for this configuration master.
int · RequiredProfile / Source

Runtime review checklist

These checks are pending. Static captures do not establish that a control saves correctly or that a workflow is usable.