← Frontend reviews · All model classes

Coflyr · Frontend form review · 10 October 2026

Configuration

Functional positions, hierarchy, allowed parts and configuration effectivity

Backend model: ReviewedGUI: Pending review4 workspaces · 32 tabs171 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 Configuration 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

Manage Configuration

Manage Configuration / Functional Position

GUI review pending

Functional position 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: Manage Configuration / Functional Position
Direct application capture · 2026-10-10T12:30:20.978828 · 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: Manage Configuration / Functional Position
Static JavaFX layout · 1280 × 408 · 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
Hierarchical Position (calculated)
hierarchicalPositionField
TextField
hierarchicalPositionProperty
editable=false
Functional Position Code *
functionalPositionCodeField
TextField
functionalPositionCodeProperty
promptText=Generated on save; editable for Custom
Generate code
generateFunctionalPositionCodeButton
Button
Generic/controller binding; inspect source
onAction=#generateFunctionalPositionCode
Functional Position Name
functionalPositionNameField
TextField
functionalPositionNameProperty
promptText=Enter functional position name
Functional Position Type
functionalPositionTypeComboBox
ComboBox
functionalPositionTypeProperty
Review requiredness, validation and display.
Functional Part Qty
functionalPartQtyField
TextField
functionalPartQtyProperty
promptText=1
Level
levelField
TextField
levelProperty
editable=false; promptText=Auto
Equipment Reference Key (Software/Boeing)
equipmentReferenceKeyField
TextField
equipmentReferenceKeyProperty
promptText=Enter the imported software location identifier
Functional Position Description
functionalPositionDescriptionField
TextField
functionalPositionDescriptionProperty
Review requiredness, validation and display.
Source evidence

Tab key: manageconfiguration--profile
Section: Entire form
FXML SHA-256: 7a39a772f122721c3826af21f64f5511024f42d5c20acc750e9dc7e599069001

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

<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?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.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.configuration.controllers.ConfigurationFormController"
      fillWidth="true"
      maxWidth="Infinity"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Functional Position"/>
        <VBox styleClass="field-group">
            <Label styleClass="field-label" text="Hierarchical Position (calculated)"/>
            <TextField fx:id="hierarchicalPositionField" editable="false" maxWidth="Infinity" styleClass="text-input"/>
        </VBox>
        <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="Functional Position Code *"/>
                        <TextField fx:id="functionalPositionCodeField" maxWidth="Infinity" prefWidth="1.0" promptText="Generated on save; editable for Custom" styleClass="text-input"/>
                        <Button fx:id="generateFunctionalPositionCodeButton" mnemonicParsing="false" onAction="#generateFunctionalPositionCode" text="Generate code" styleClass="label-action-button"/>
                    </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 styleClass="field-label" text="Functional Position Name"/>
                        <TextField fx:id="functionalPositionNameField" maxWidth="Infinity" prefWidth="1.0" promptText="Enter functional position 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 styleClass="field-label" text="Functional Position Type"/>
                        <ComboBox fx:id="functionalPositionTypeComboBox" 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 styleClass="field-label" text="Functional Part Qty"/>
                        <TextField fx:id="functionalPartQtyField" maxWidth="Infinity" prefWidth="1.0" promptText="1" 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="Level"/>
                        <TextField fx:id="levelField" editable="false" focusTraversable="false" maxWidth="Infinity" prefWidth="1.0" promptText="Auto" styleClass="text-input"/>
                    </children>
                </VBox>
                <VBox fx:id="equipmentReferenceKeyGroup" 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="Equipment Reference Key (Software/Boeing)"/>
                        <TextField fx:id="equipmentReferenceKeyField" maxWidth="Infinity" prefWidth="1.0" promptText="Enter the imported software location identifier" 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="Functional Position Description"/>
                        <TextField fx:id="functionalPositionDescriptionField" 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.ConfigurationFormController
package com.coflyr.fe.configuration.controllers;

import com.coflyr.fe.configuration.viewmodels.ConfigurationViewModel;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.fxml.FXML;
import javafx.beans.binding.Bindings;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
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 ConfigurationFormController {
    private final ConfigurationViewModel viewModel;

    @FXML private TextField functionalPositionCodeField;
    @FXML private TextField hierarchicalPositionField;
    @FXML private Button generateFunctionalPositionCodeButton;
    @FXML private TextField functionalPositionNameField;
    @FXML private ComboBox<String> functionalPositionTypeComboBox;
    @FXML private TextField functionalPositionDescriptionField;
    @FXML private TextField functionalPartQtyField;
    @FXML private CheckBox positionLeftHandCheckBox;
    @FXML private CheckBox positionRightHandCheckBox;
    @FXML private CheckBox positionForwardCheckBox;
    @FXML private CheckBox positionAftCheckBox;
    @FXML private TextField positionNumberStartField;
    @FXML private TextField positionNumberEndField;
    @FXML private TextField levelField;
    @FXML private TextField sequenceField;
    @FXML private DbSearchLookupFieldController parentHardwarePositionLookupController;
    @FXML private DbSearchLookupFieldController approvedPartMasterLookupController;
    @FXML private DbSearchLookupFieldController allowableAlternatePartLookupController;
    @FXML private DbSearchLookupFieldController parentConfigurationLookupController;
    @FXML private DbSearchLookupFieldController ataLookupController;
    @FXML private DbSearchLookupFieldController ownerBusinessPartnerLookupController;
    @FXML private DbSearchLookupFieldController locationReferenceLookupController;
    // Booked/as-flying comparison fields are disabled until configuration checker/import is implemented.
    // @FXML private DbSearchLookupFieldController bookedConfigurationMasterLookupController;
    // @FXML private DbSearchLookupFieldController asFlyingConfigurationMasterLookupController;
    @FXML private TextField aircraftSystemCodeField;
    @FXML private TextField equipmentReferenceKeyField;
    @FXML private VBox equipmentReferenceKeyGroup;
    @FXML private TextField effectiveDateField;
    @FXML private TextField missionConfigurationCodeField;
    @FXML private TextField statusReportInformationField;
    @FXML private TextField massStorageDeviceCodeField;
    @FXML private TextField rescueKitCodeField;
    @FXML private TextField zoneCodeField;
    @FXML private TextField panelCodeField;
    @FXML private CheckBox massStorageDeviceCheckBox;
    @FXML private CheckBox rescueKitCheckBox;
    @FXML private TextField ipcReferenceField;
    @FXML private CheckBox groupCheckBox;
    @FXML private CheckBox optionalCheckBox;
    // Inactive state is handled through shared audit/status fields.
    // @FXML private CheckBox inactiveCheckBox;
    @FXML private CheckBox lruCheckBox;
    @FXML private CheckBox qecCheckBox;
    @FXML private CheckBox asFlyingFpCheckBox;
    @FXML private CheckBox informationOnlyCheckBox;
    @FXML private CheckBox excludeAsFlyingFpCheckBox;
    // @FXML private CheckBox mandatoryCheckBox;
    // Preferred selection belongs to part/effectivity relationship, not the configuration position.
    // @FXML private CheckBox preferredCheckBox;
    // Inspection requirements are managed through maintenance program/effectivity links.
    // @FXML private CheckBox hasRequirementCheckBox;
    // @FXML private CheckBox hasSoftRequirementCheckBox;
    // Reusable subtree roots are not required because child structures are assigned through Configuration/Effectivity.
    // @FXML private CheckBox subTreeRootCheckBox;
    // FP code uniqueness is enforced as a configuration rule, not a per-row user choice.
    // @FXML private CheckBox uniqueFpCodeRequiredCheckBox;
    @FXML private CheckBox allowAlternatePartsCheckBox;
    // Sub-configuration inclusion is derived from ParentConfigurationId hierarchy during checks.
    // @FXML private CheckBox includeSubConfigurationsCheckBox;
    // Selection is UI state and must not be persisted.
    // @FXML private CheckBox selectedCheckBox;
    // Editability is controlled by audit/status workflow, not a configuration row flag.
    // @FXML private CheckBox changeableCheckBox;
    // Changeable Reason is disabled with Changeable.
    // @FXML private TextField changeableReasonField;
    // @FXML private TextField bookedConfigurationCodeField;
    // @FXML private TextField asFlyingConfigurationCodeField;
    // Reusable subtree assignment is not required because child parts are assigned through Configuration/Effectivity.
    // @FXML private TextField subTreeMasterIdField;

    public ConfigurationFormController(ConfigurationViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        if (hierarchicalPositionField != null) {
            hierarchicalPositionField.textProperty().bind(viewModel.hierarchicalPositionProperty());
        }
        bind(sequenceField, viewModel.sequenceProperty());
        LookupFieldBinding.bind(parentHardwarePositionLookupController, "configurations", "Hardware Host", "Search hardware position", viewModel.parentHardwarePositionIdProperty(), viewModel.parentHardwarePositionLookupQueryProperty());
        LookupFieldBinding.bind(approvedPartMasterLookupController, "parts", "Approved Part", "Search approved part", viewModel.approvedPartMasterIdProperty(), viewModel.approvedPartMasterLookupQueryProperty());
        LookupFieldBinding.bind(allowableAlternatePartLookupController, "parts", "Alternate Part", "Search alternate part", viewModel.allowableAlternatePartIdProperty(), viewModel.allowableAlternatePartLookupQueryProperty());
        if (functionalPositionTypeComboBox != null) {
            functionalPositionTypeComboBox.setItems(viewModel.functionalPositionTypes());
            functionalPositionTypeComboBox.valueProperty().bindBidirectional(viewModel.functionalPositionTypeProperty());
        }
        if (equipmentReferenceKeyGroup != null) {
            equipmentReferenceKeyGroup.visibleProperty().bind(Bindings.createBooleanBinding(
                    () -> "Software".equalsIgnoreCase(viewModel.functionalPositionTypeProperty().get()),
                    viewModel.functionalPositionTypeProperty()));
            equipmentReferenceKeyGroup.managedProperty().bind(equipmentReferenceKeyGroup.visibleProperty());
        }
        bind(functionalPositionCodeField, viewModel.functionalPositionCodeProperty());
        if (functionalPositionCodeField != null) {
            functionalPositionCodeField.editableProperty().bind(viewModel.fpGeneratorModeProperty().isEqualTo("Custom"));
            functionalPositionCodeField.focusTraversableProperty().bind(viewModel.fpGeneratorModeProperty().isEqualTo("Custom"));
        }
        if (generateFunctionalPositionCodeButton != null) {
            generateFunctionalPositionCodeButton.disableProperty().bind(
                    viewModel.fpGeneratorModeProperty().isEqualTo("Custom").or(
                            Bindings.createBooleanBinding(
                                    () -> viewModel.functionalPositionNameProperty().get() == null
                                            || viewModel.functionalPositionNameProperty().get().isBlank(),
                                    viewModel.functionalPositionNameProperty())));
        }
        bind(functionalPositionNameField, viewModel.functionalPositionNameProperty());
        bind(functionalPositionDescriptionField, viewModel.functionalPositionDescriptionProperty());
        bind(functionalPartQtyField, viewModel.functionalPartQtyProperty());
        bind(positionLeftHandCheckBox, viewModel.positionLeftHandProperty());
        bind(positionRightHandCheckBox, viewModel.positionRightHandProperty());
        bind(positionForwardCheckBox, viewModel.positionForwardProperty());
        bind(positionAftCheckBox, viewModel.positionAftProperty());
        bind(positionNumberStartField, viewModel.positionNumberStartProperty());
        bind(positionNumberEndField, viewModel.positionNumberEndProperty());
        enforceExclusive(positionLeftHandCheckBox, positionRightHandCheckBox);
        enforceExclusive(positionForwardCheckBox, positionAftCheckBox);
        bind(levelField, viewModel.levelProperty());
        bind(aircraftSystemCodeField, viewModel.aircraftSystemCodeProperty());
        bind(equipmentReferenceKeyField, viewModel.equipmentReferenceKeyProperty());
        bind(effectiveDateField, viewModel.effectiveDateProperty());
        bind(missionConfigurationCodeField, viewModel.missionConfigurationCodeProperty());
        bind(statusReportInformationField, viewModel.statusReportInformationProperty());
        bind(massStorageDeviceCodeField, viewModel.massStorageDeviceCodeProperty());
        bind(rescueKitCodeField, viewModel.rescueKitCodeProperty());
        bind(zoneCodeField, viewModel.zoneCodeProperty());
        bind(panelCodeField, viewModel.panelCodeProperty());
        bind(massStorageDeviceCheckBox, viewModel.massStorageDeviceProperty());
        bind(rescueKitCheckBox, viewModel.rescueKitProperty());
        bind(ipcReferenceField, viewModel.ipcReferenceProperty());
        // bind(changeableReasonField, viewModel.changeableReasonProperty());
        // bind(bookedConfigurationCodeField, viewModel.bookedConfigurationCodeProperty());
        // bind(asFlyingConfigurationCodeField, viewModel.asFlyingConfigurationCodeProperty());
        // bind(subTreeMasterIdField, viewModel.subTreeMasterIdProperty());
        bind(groupCheckBox, viewModel.groupProperty());
        bind(optionalCheckBox, viewModel.optionalProperty());
        // bind(inactiveCheckBox, viewModel.inactiveProperty());
        bind(lruCheckBox, viewModel.lruProperty());
        bind(qecCheckBox, viewModel.qecProperty());
        bind(asFlyingFpCheckBox, viewModel.asFlyingFpProperty());
        bind(informationOnlyCheckBox, viewModel.informationOnlyProperty());
        bind(excludeAsFlyingFpCheckBox, viewModel.excludeAsFlyingFpProperty());
        // bind(mandatoryCheckBox, viewModel.mandatoryProperty());
        // bind(preferredCheckBox, viewModel.preferredProperty());
        // bind(hasRequirementCheckBox, viewModel.hasRequirementProperty());
        // bind(hasSoftRequirementCheckBox, viewModel.hasSoftRequirementProperty());
        // bind(subTreeRootCheckBox, viewModel.subTreeRootProperty());
        // bind(uniqueFpCodeRequiredCheckBox, viewModel.uniqueFpCodeRequiredProperty());
        bind(allowAlternatePartsCheckBox, viewModel.allowAlternatePartsProperty());
        // bind(includeSubConfigurationsCheckBox, viewModel.includeSubConfigurationsProperty());
        // bind(selectedCheckBox, viewModel.selectedProperty());
        // bind(changeableCheckBox, viewModel.changeableProperty());
        if (parentConfigurationLookupController != null) {
            LookupFieldBinding.bind(
                    parentConfigurationLookupController,
                    "configurations",
                    "Parent Configuration",
                    "Search parent configuration",
                    viewModel.parentConfigurationIdProperty(),
                    viewModel.parentConfigurationLookupQueryProperty()
            );
        }
        if (ataLookupController != null) {
            LookupFieldBinding.bind(ataLookupController, "ata", "ATA", "Search ATA", viewModel.ataIdProperty(), viewModel.ataLookupQueryProperty());
        }
        if (ownerBusinessPartnerLookupController != null) {
            LookupFieldBinding.bind(ownerBusinessPartnerLookupController, "business-partners", "Owner Business Partner", "Search owner business partner", viewModel.ownerBusinessPartnerIdProperty(), viewModel.ownerBusinessPartnerLookupQueryProperty());
        }
        if (locationReferenceLookupController != null) {
            LookupFieldBinding.bind(
                    locationReferenceLookupController,
                    "locationreferences",
                    "Physical Location",
                    "Search physical location",
                    viewModel.locationReferenceIdProperty(),
                    viewModel.locationReferenceLookupQueryProperty()
            );
        }
        // if (bookedConfigurationMasterLookupController != null) {
        //     LookupFieldBinding.bind(bookedConfigurationMasterLookupController, "configuration-masters", "Booked Configuration Master", "Search booked configuration master", viewModel.bookedConfigurationMasterIdProperty(), viewModel.bookedConfigurationMasterLookupQueryProperty());
        // }
        // if (asFlyingConfigurationMasterLookupController != null) {
        //     LookupFieldBinding.bind(asFlyingConfigurationMasterLookupController, "configuration-masters", "As-Flying Configuration Master", "Search as-flying configuration master", viewModel.asFlyingConfigurationMasterIdProperty(), viewModel.asFlyingConfigurationMasterLookupQueryProperty());
        // }
    }

    private void bind(TextField field, javafx.beans.property.StringProperty property) {
        if (field != null) {
            field.textProperty().bindBidirectional(property);
        }
    }

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

    private void enforceExclusive(CheckBox first, CheckBox second) {
        if (first == null || second == null) return;
        first.setOnAction(event -> { if (first.isSelected()) second.setSelected(false); });
        second.setOnAction(event -> { if (second.isSelected()) first.setSelected(false); });
    }

    @FXML
    private void generateFunctionalPositionCode() {
        if (viewModel.functionalPositionNameProperty().get() == null
                || viewModel.functionalPositionNameProperty().get().isBlank()) return;
        String mode = viewModel.fpGeneratorModeProperty().get();
        if (mode == null || mode.isBlank() || "Custom".equalsIgnoreCase(mode)) return;
        String code = switch (mode) {
            case "Number" -> "1";
            case "Incremental" -> "FP.1";
            case "PartNumber" -> suffix(viewModel.approvedPartMasterLookupQueryProperty().get(), "FP");
            case "S1000D" -> suffix(viewModel.equipmentReferenceKeyProperty().get(), "FP");
            case "Position", "Automatic", "DefinedByParameter" -> suffix(viewModel.functionalPositionNameProperty().get(), "FP");
            default -> "FP.1";
        };
        viewModel.functionalPositionCodeProperty().set(code);
    }

    private static String suffix(String value, String fallback) {
        String source = value == null ? "" : value.trim().replaceAll("[^A-Za-z0-9_-]+", "-");
        source = source.replaceAll("^-+|-+$", "");
        return (source.isBlank() ? fallback : source) + ".1";
    }
}

Manage Configuration / Direction & Numbering

GUI review pending

Direction, sequence and number range.

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: Manage Configuration / Direction & Numbering
Direct application capture · 2026-10-10T12:30:24.385634 · 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: Manage Configuration / Direction & Numbering
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
Sequence
sequenceField
TextField
sequenceProperty
promptText=Non-negative whole number
LH (Left-hand)
positionLeftHandCheckBox
CheckBox
positionLeftHandProperty
Review requiredness, validation and display.
RH (Right-hand)
positionRightHandCheckBox
CheckBox
positionRightHandProperty
Review requiredness, validation and display.
FWD (Forward)
positionForwardCheckBox
CheckBox
positionForwardProperty
Review requiredness, validation and display.
AFT (Aft)
positionAftCheckBox
CheckBox
positionAftProperty
Review requiredness, validation and display.
Number Start
positionNumberStartField
TextField
positionNumberStartProperty
promptText=Optional inclusive start
Number End
positionNumberEndField
TextField
positionNumberEndProperty
promptText=Optional inclusive end
Source evidence

Tab key: manageconfiguration--position
Section: Entire form
FXML SHA-256: b603ee95ea9793c11e54c3519f816c48b372df5419db4f499aef0914bae5c6e8

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

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

<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.configuration.controllers.ConfigurationFormController"
      fillWidth="true"
      maxWidth="Infinity"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Direction &amp; Numbering"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.rowIndex="0"><Label text="Sequence" styleClass="field-label"/><TextField fx:id="sequenceField" promptText="Non-negative whole number" styleClass="text-input"/></VBox>

                <HBox spacing="24" alignment="CENTER_LEFT" GridPane.rowIndex="1" GridPane.columnSpan="3">
                    <CheckBox fx:id="positionLeftHandCheckBox" text="LH (Left-hand)" styleClass="form-check-box"/>
                    <CheckBox fx:id="positionRightHandCheckBox" text="RH (Right-hand)" styleClass="form-check-box"/>
                    <CheckBox fx:id="positionForwardCheckBox" text="FWD (Forward)" styleClass="form-check-box"/>
                    <CheckBox fx:id="positionAftCheckBox" text="AFT (Aft)" styleClass="form-check-box"/>
                </HBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="2"><children><Label text="Number Start" styleClass="field-label"/><TextField fx:id="positionNumberStartField" promptText="Optional inclusive start" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="2"><children><Label text="Number End" styleClass="field-label"/><TextField fx:id="positionNumberEndField" promptText="Optional inclusive end" 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.ConfigurationFormController
package com.coflyr.fe.configuration.controllers;

import com.coflyr.fe.configuration.viewmodels.ConfigurationViewModel;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.fxml.FXML;
import javafx.beans.binding.Bindings;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
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 ConfigurationFormController {
    private final ConfigurationViewModel viewModel;

    @FXML private TextField functionalPositionCodeField;
    @FXML private TextField hierarchicalPositionField;
    @FXML private Button generateFunctionalPositionCodeButton;
    @FXML private TextField functionalPositionNameField;
    @FXML private ComboBox<String> functionalPositionTypeComboBox;
    @FXML private TextField functionalPositionDescriptionField;
    @FXML private TextField functionalPartQtyField;
    @FXML private CheckBox positionLeftHandCheckBox;
    @FXML private CheckBox positionRightHandCheckBox;
    @FXML private CheckBox positionForwardCheckBox;
    @FXML private CheckBox positionAftCheckBox;
    @FXML private TextField positionNumberStartField;
    @FXML private TextField positionNumberEndField;
    @FXML private TextField levelField;
    @FXML private TextField sequenceField;
    @FXML private DbSearchLookupFieldController parentHardwarePositionLookupController;
    @FXML private DbSearchLookupFieldController approvedPartMasterLookupController;
    @FXML private DbSearchLookupFieldController allowableAlternatePartLookupController;
    @FXML private DbSearchLookupFieldController parentConfigurationLookupController;
    @FXML private DbSearchLookupFieldController ataLookupController;
    @FXML private DbSearchLookupFieldController ownerBusinessPartnerLookupController;
    @FXML private DbSearchLookupFieldController locationReferenceLookupController;
    // Booked/as-flying comparison fields are disabled until configuration checker/import is implemented.
    // @FXML private DbSearchLookupFieldController bookedConfigurationMasterLookupController;
    // @FXML private DbSearchLookupFieldController asFlyingConfigurationMasterLookupController;
    @FXML private TextField aircraftSystemCodeField;
    @FXML private TextField equipmentReferenceKeyField;
    @FXML private VBox equipmentReferenceKeyGroup;
    @FXML private TextField effectiveDateField;
    @FXML private TextField missionConfigurationCodeField;
    @FXML private TextField statusReportInformationField;
    @FXML private TextField massStorageDeviceCodeField;
    @FXML private TextField rescueKitCodeField;
    @FXML private TextField zoneCodeField;
    @FXML private TextField panelCodeField;
    @FXML private CheckBox massStorageDeviceCheckBox;
    @FXML private CheckBox rescueKitCheckBox;
    @FXML private TextField ipcReferenceField;
    @FXML private CheckBox groupCheckBox;
    @FXML private CheckBox optionalCheckBox;
    // Inactive state is handled through shared audit/status fields.
    // @FXML private CheckBox inactiveCheckBox;
    @FXML private CheckBox lruCheckBox;
    @FXML private CheckBox qecCheckBox;
    @FXML private CheckBox asFlyingFpCheckBox;
    @FXML private CheckBox informationOnlyCheckBox;
    @FXML private CheckBox excludeAsFlyingFpCheckBox;
    // @FXML private CheckBox mandatoryCheckBox;
    // Preferred selection belongs to part/effectivity relationship, not the configuration position.
    // @FXML private CheckBox preferredCheckBox;
    // Inspection requirements are managed through maintenance program/effectivity links.
    // @FXML private CheckBox hasRequirementCheckBox;
    // @FXML private CheckBox hasSoftRequirementCheckBox;
    // Reusable subtree roots are not required because child structures are assigned through Configuration/Effectivity.
    // @FXML private CheckBox subTreeRootCheckBox;
    // FP code uniqueness is enforced as a configuration rule, not a per-row user choice.
    // @FXML private CheckBox uniqueFpCodeRequiredCheckBox;
    @FXML private CheckBox allowAlternatePartsCheckBox;
    // Sub-configuration inclusion is derived from ParentConfigurationId hierarchy during checks.
    // @FXML private CheckBox includeSubConfigurationsCheckBox;
    // Selection is UI state and must not be persisted.
    // @FXML private CheckBox selectedCheckBox;
    // Editability is controlled by audit/status workflow, not a configuration row flag.
    // @FXML private CheckBox changeableCheckBox;
    // Changeable Reason is disabled with Changeable.
    // @FXML private TextField changeableReasonField;
    // @FXML private TextField bookedConfigurationCodeField;
    // @FXML private TextField asFlyingConfigurationCodeField;
    // Reusable subtree assignment is not required because child parts are assigned through Configuration/Effectivity.
    // @FXML private TextField subTreeMasterIdField;

    public ConfigurationFormController(ConfigurationViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        if (hierarchicalPositionField != null) {
            hierarchicalPositionField.textProperty().bind(viewModel.hierarchicalPositionProperty());
        }
        bind(sequenceField, viewModel.sequenceProperty());
        LookupFieldBinding.bind(parentHardwarePositionLookupController, "configurations", "Hardware Host", "Search hardware position", viewModel.parentHardwarePositionIdProperty(), viewModel.parentHardwarePositionLookupQueryProperty());
        LookupFieldBinding.bind(approvedPartMasterLookupController, "parts", "Approved Part", "Search approved part", viewModel.approvedPartMasterIdProperty(), viewModel.approvedPartMasterLookupQueryProperty());
        LookupFieldBinding.bind(allowableAlternatePartLookupController, "parts", "Alternate Part", "Search alternate part", viewModel.allowableAlternatePartIdProperty(), viewModel.allowableAlternatePartLookupQueryProperty());
        if (functionalPositionTypeComboBox != null) {
            functionalPositionTypeComboBox.setItems(viewModel.functionalPositionTypes());
            functionalPositionTypeComboBox.valueProperty().bindBidirectional(viewModel.functionalPositionTypeProperty());
        }
        if (equipmentReferenceKeyGroup != null) {
            equipmentReferenceKeyGroup.visibleProperty().bind(Bindings.createBooleanBinding(
                    () -> "Software".equalsIgnoreCase(viewModel.functionalPositionTypeProperty().get()),
                    viewModel.functionalPositionTypeProperty()));
            equipmentReferenceKeyGroup.managedProperty().bind(equipmentReferenceKeyGroup.visibleProperty());
        }
        bind(functionalPositionCodeField, viewModel.functionalPositionCodeProperty());
        if (functionalPositionCodeField != null) {
            functionalPositionCodeField.editableProperty().bind(viewModel.fpGeneratorModeProperty().isEqualTo("Custom"));
            functionalPositionCodeField.focusTraversableProperty().bind(viewModel.fpGeneratorModeProperty().isEqualTo("Custom"));
        }
        if (generateFunctionalPositionCodeButton != null) {
            generateFunctionalPositionCodeButton.disableProperty().bind(
                    viewModel.fpGeneratorModeProperty().isEqualTo("Custom").or(
                            Bindings.createBooleanBinding(
                                    () -> viewModel.functionalPositionNameProperty().get() == null
                                            || viewModel.functionalPositionNameProperty().get().isBlank(),
                                    viewModel.functionalPositionNameProperty())));
        }
        bind(functionalPositionNameField, viewModel.functionalPositionNameProperty());
        bind(functionalPositionDescriptionField, viewModel.functionalPositionDescriptionProperty());
        bind(functionalPartQtyField, viewModel.functionalPartQtyProperty());
        bind(positionLeftHandCheckBox, viewModel.positionLeftHandProperty());
        bind(positionRightHandCheckBox, viewModel.positionRightHandProperty());
        bind(positionForwardCheckBox, viewModel.positionForwardProperty());
        bind(positionAftCheckBox, viewModel.positionAftProperty());
        bind(positionNumberStartField, viewModel.positionNumberStartProperty());
        bind(positionNumberEndField, viewModel.positionNumberEndProperty());
        enforceExclusive(positionLeftHandCheckBox, positionRightHandCheckBox);
        enforceExclusive(positionForwardCheckBox, positionAftCheckBox);
        bind(levelField, viewModel.levelProperty());
        bind(aircraftSystemCodeField, viewModel.aircraftSystemCodeProperty());
        bind(equipmentReferenceKeyField, viewModel.equipmentReferenceKeyProperty());
        bind(effectiveDateField, viewModel.effectiveDateProperty());
        bind(missionConfigurationCodeField, viewModel.missionConfigurationCodeProperty());
        bind(statusReportInformationField, viewModel.statusReportInformationProperty());
        bind(massStorageDeviceCodeField, viewModel.massStorageDeviceCodeProperty());
        bind(rescueKitCodeField, viewModel.rescueKitCodeProperty());
        bind(zoneCodeField, viewModel.zoneCodeProperty());
        bind(panelCodeField, viewModel.panelCodeProperty());
        bind(massStorageDeviceCheckBox, viewModel.massStorageDeviceProperty());
        bind(rescueKitCheckBox, viewModel.rescueKitProperty());
        bind(ipcReferenceField, viewModel.ipcReferenceProperty());
        // bind(changeableReasonField, viewModel.changeableReasonProperty());
        // bind(bookedConfigurationCodeField, viewModel.bookedConfigurationCodeProperty());
        // bind(asFlyingConfigurationCodeField, viewModel.asFlyingConfigurationCodeProperty());
        // bind(subTreeMasterIdField, viewModel.subTreeMasterIdProperty());
        bind(groupCheckBox, viewModel.groupProperty());
        bind(optionalCheckBox, viewModel.optionalProperty());
        // bind(inactiveCheckBox, viewModel.inactiveProperty());
        bind(lruCheckBox, viewModel.lruProperty());
        bind(qecCheckBox, viewModel.qecProperty());
        bind(asFlyingFpCheckBox, viewModel.asFlyingFpProperty());
        bind(informationOnlyCheckBox, viewModel.informationOnlyProperty());
        bind(excludeAsFlyingFpCheckBox, viewModel.excludeAsFlyingFpProperty());
        // bind(mandatoryCheckBox, viewModel.mandatoryProperty());
        // bind(preferredCheckBox, viewModel.preferredProperty());
        // bind(hasRequirementCheckBox, viewModel.hasRequirementProperty());
        // bind(hasSoftRequirementCheckBox, viewModel.hasSoftRequirementProperty());
        // bind(subTreeRootCheckBox, viewModel.subTreeRootProperty());
        // bind(uniqueFpCodeRequiredCheckBox, viewModel.uniqueFpCodeRequiredProperty());
        bind(allowAlternatePartsCheckBox, viewModel.allowAlternatePartsProperty());
        // bind(includeSubConfigurationsCheckBox, viewModel.includeSubConfigurationsProperty());
        // bind(selectedCheckBox, viewModel.selectedProperty());
        // bind(changeableCheckBox, viewModel.changeableProperty());
        if (parentConfigurationLookupController != null) {
            LookupFieldBinding.bind(
                    parentConfigurationLookupController,
                    "configurations",
                    "Parent Configuration",
                    "Search parent configuration",
                    viewModel.parentConfigurationIdProperty(),
                    viewModel.parentConfigurationLookupQueryProperty()
            );
        }
        if (ataLookupController != null) {
            LookupFieldBinding.bind(ataLookupController, "ata", "ATA", "Search ATA", viewModel.ataIdProperty(), viewModel.ataLookupQueryProperty());
        }
        if (ownerBusinessPartnerLookupController != null) {
            LookupFieldBinding.bind(ownerBusinessPartnerLookupController, "business-partners", "Owner Business Partner", "Search owner business partner", viewModel.ownerBusinessPartnerIdProperty(), viewModel.ownerBusinessPartnerLookupQueryProperty());
        }
        if (locationReferenceLookupController != null) {
            LookupFieldBinding.bind(
                    locationReferenceLookupController,
                    "locationreferences",
                    "Physical Location",
                    "Search physical location",
                    viewModel.locationReferenceIdProperty(),
                    viewModel.locationReferenceLookupQueryProperty()
            );
        }
        // if (bookedConfigurationMasterLookupController != null) {
        //     LookupFieldBinding.bind(bookedConfigurationMasterLookupController, "configuration-masters", "Booked Configuration Master", "Search booked configuration master", viewModel.bookedConfigurationMasterIdProperty(), viewModel.bookedConfigurationMasterLookupQueryProperty());
        // }
        // if (asFlyingConfigurationMasterLookupController != null) {
        //     LookupFieldBinding.bind(asFlyingConfigurationMasterLookupController, "configuration-masters", "As-Flying Configuration Master", "Search as-flying configuration master", viewModel.asFlyingConfigurationMasterIdProperty(), viewModel.asFlyingConfigurationMasterLookupQueryProperty());
        // }
    }

    private void bind(TextField field, javafx.beans.property.StringProperty property) {
        if (field != null) {
            field.textProperty().bindBidirectional(property);
        }
    }

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

    private void enforceExclusive(CheckBox first, CheckBox second) {
        if (first == null || second == null) return;
        first.setOnAction(event -> { if (first.isSelected()) second.setSelected(false); });
        second.setOnAction(event -> { if (second.isSelected()) first.setSelected(false); });
    }

    @FXML
    private void generateFunctionalPositionCode() {
        if (viewModel.functionalPositionNameProperty().get() == null
                || viewModel.functionalPositionNameProperty().get().isBlank()) return;
        String mode = viewModel.fpGeneratorModeProperty().get();
        if (mode == null || mode.isBlank() || "Custom".equalsIgnoreCase(mode)) return;
        String code = switch (mode) {
            case "Number" -> "1";
            case "Incremental" -> "FP.1";
            case "PartNumber" -> suffix(viewModel.approvedPartMasterLookupQueryProperty().get(), "FP");
            case "S1000D" -> suffix(viewModel.equipmentReferenceKeyProperty().get(), "FP");
            case "Position", "Automatic", "DefinedByParameter" -> suffix(viewModel.functionalPositionNameProperty().get(), "FP");
            default -> "FP.1";
        };
        viewModel.functionalPositionCodeProperty().set(code);
    }

    private static String suffix(String value, String fallback) {
        String source = value == null ? "" : value.trim().replaceAll("[^A-Za-z0-9_-]+", "-");
        source = source.replaceAll("^-+|-+$", "");
        return (source.isBlank() ? fallback : source) + ".1";
    }
}

Manage Configuration / Hierarchy & Options

GUI review pending

Parent configuration and flags.

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: Manage Configuration / Hierarchy & Options
Direct application capture · 2026-10-10T12:30:27.822409 · 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: Manage Configuration / Hierarchy & Options
Static JavaFX layout · 1280 × 280 · 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
Parent Configuration
parentConfigurationLookup
UUID search lookup
parentConfigurationIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Group
groupCheckBox
CheckBox
groupProperty
Review requiredness, validation and display.
Optional
optionalCheckBox
CheckBox
optionalProperty
Review requiredness, validation and display.
LRU
lruCheckBox
CheckBox
lruProperty
Review requiredness, validation and display.
QEC
qecCheckBox
CheckBox
qecProperty
Review requiredness, validation and display.
As-Flying FP
asFlyingFpCheckBox
CheckBox
asFlyingFpProperty
Review requiredness, validation and display.
Exclude As-Flying FP
excludeAsFlyingFpCheckBox
CheckBox
excludeAsFlyingFpProperty
Review requiredness, validation and display.
Information Only
informationOnlyCheckBox
CheckBox
informationOnlyProperty
Review requiredness, validation and display.
Allow Alternate Parts
allowAlternatePartsCheckBox
CheckBox
allowAlternatePartsProperty
Review requiredness, validation and display.
Source evidence

Tab key: manageconfiguration--hierarchyoptions
Section: Entire form
FXML SHA-256: 5017f9a5c0170a8f9d498dc0d1a5119f574af6f34969f7d484f55efd702a825c

coflyr-fe/src/main/resources/com/coflyr/fe/configuration/views/configuration-hierarchy-options-form.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1"
 fx:controller="com.coflyr.fe.configuration.controllers.ConfigurationFormController"
 fillWidth="true" maxWidth="Infinity" spacing="8" styleClass="standard-section-flat">
 <Label text="Hierarchy &amp; Options" styleClass="section-title"/>
 <VBox fillWidth="true" maxWidth="Infinity" minWidth="0.0" styleClass="field-group">
                    <children>
                        <fx:include fx:id="parentConfigurationLookup" source="../../views/controls/db-search-lookup-field.fxml"/>
                    </children>
                </VBox>
        <Label styleClass="section-title" text="Flags"/>
        <FlowPane hgap="10" styleClass="form-checkbox-pane" vgap="8">
            <children>
                <CheckBox fx:id="groupCheckBox" focusTraversable="false" styleClass="form-check-box" text="Group"/>
                <CheckBox fx:id="optionalCheckBox" focusTraversable="false" styleClass="form-check-box" text="Optional"/>
                <CheckBox fx:id="lruCheckBox" focusTraversable="false" styleClass="form-check-box" text="LRU"/>
                <CheckBox fx:id="qecCheckBox" focusTraversable="false" styleClass="form-check-box" text="QEC"/>
                <CheckBox fx:id="asFlyingFpCheckBox" focusTraversable="false" styleClass="form-check-box" text="As-Flying FP"/>
                <CheckBox fx:id="excludeAsFlyingFpCheckBox" focusTraversable="false" styleClass="form-check-box" text="Exclude As-Flying FP"/>
                <CheckBox fx:id="informationOnlyCheckBox" focusTraversable="false" styleClass="form-check-box" text="Information Only"/>
                <CheckBox fx:id="allowAlternatePartsCheckBox" focusTraversable="false" styleClass="form-check-box" text="Allow Alternate Parts"/>
            </children>
        </FlowPane>
</VBox>
Controller: com.coflyr.fe.configuration.controllers.ConfigurationFormController
package com.coflyr.fe.configuration.controllers;

import com.coflyr.fe.configuration.viewmodels.ConfigurationViewModel;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.fxml.FXML;
import javafx.beans.binding.Bindings;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
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 ConfigurationFormController {
    private final ConfigurationViewModel viewModel;

    @FXML private TextField functionalPositionCodeField;
    @FXML private TextField hierarchicalPositionField;
    @FXML private Button generateFunctionalPositionCodeButton;
    @FXML private TextField functionalPositionNameField;
    @FXML private ComboBox<String> functionalPositionTypeComboBox;
    @FXML private TextField functionalPositionDescriptionField;
    @FXML private TextField functionalPartQtyField;
    @FXML private CheckBox positionLeftHandCheckBox;
    @FXML private CheckBox positionRightHandCheckBox;
    @FXML private CheckBox positionForwardCheckBox;
    @FXML private CheckBox positionAftCheckBox;
    @FXML private TextField positionNumberStartField;
    @FXML private TextField positionNumberEndField;
    @FXML private TextField levelField;
    @FXML private TextField sequenceField;
    @FXML private DbSearchLookupFieldController parentHardwarePositionLookupController;
    @FXML private DbSearchLookupFieldController approvedPartMasterLookupController;
    @FXML private DbSearchLookupFieldController allowableAlternatePartLookupController;
    @FXML private DbSearchLookupFieldController parentConfigurationLookupController;
    @FXML private DbSearchLookupFieldController ataLookupController;
    @FXML private DbSearchLookupFieldController ownerBusinessPartnerLookupController;
    @FXML private DbSearchLookupFieldController locationReferenceLookupController;
    // Booked/as-flying comparison fields are disabled until configuration checker/import is implemented.
    // @FXML private DbSearchLookupFieldController bookedConfigurationMasterLookupController;
    // @FXML private DbSearchLookupFieldController asFlyingConfigurationMasterLookupController;
    @FXML private TextField aircraftSystemCodeField;
    @FXML private TextField equipmentReferenceKeyField;
    @FXML private VBox equipmentReferenceKeyGroup;
    @FXML private TextField effectiveDateField;
    @FXML private TextField missionConfigurationCodeField;
    @FXML private TextField statusReportInformationField;
    @FXML private TextField massStorageDeviceCodeField;
    @FXML private TextField rescueKitCodeField;
    @FXML private TextField zoneCodeField;
    @FXML private TextField panelCodeField;
    @FXML private CheckBox massStorageDeviceCheckBox;
    @FXML private CheckBox rescueKitCheckBox;
    @FXML private TextField ipcReferenceField;
    @FXML private CheckBox groupCheckBox;
    @FXML private CheckBox optionalCheckBox;
    // Inactive state is handled through shared audit/status fields.
    // @FXML private CheckBox inactiveCheckBox;
    @FXML private CheckBox lruCheckBox;
    @FXML private CheckBox qecCheckBox;
    @FXML private CheckBox asFlyingFpCheckBox;
    @FXML private CheckBox informationOnlyCheckBox;
    @FXML private CheckBox excludeAsFlyingFpCheckBox;
    // @FXML private CheckBox mandatoryCheckBox;
    // Preferred selection belongs to part/effectivity relationship, not the configuration position.
    // @FXML private CheckBox preferredCheckBox;
    // Inspection requirements are managed through maintenance program/effectivity links.
    // @FXML private CheckBox hasRequirementCheckBox;
    // @FXML private CheckBox hasSoftRequirementCheckBox;
    // Reusable subtree roots are not required because child structures are assigned through Configuration/Effectivity.
    // @FXML private CheckBox subTreeRootCheckBox;
    // FP code uniqueness is enforced as a configuration rule, not a per-row user choice.
    // @FXML private CheckBox uniqueFpCodeRequiredCheckBox;
    @FXML private CheckBox allowAlternatePartsCheckBox;
    // Sub-configuration inclusion is derived from ParentConfigurationId hierarchy during checks.
    // @FXML private CheckBox includeSubConfigurationsCheckBox;
    // Selection is UI state and must not be persisted.
    // @FXML private CheckBox selectedCheckBox;
    // Editability is controlled by audit/status workflow, not a configuration row flag.
    // @FXML private CheckBox changeableCheckBox;
    // Changeable Reason is disabled with Changeable.
    // @FXML private TextField changeableReasonField;
    // @FXML private TextField bookedConfigurationCodeField;
    // @FXML private TextField asFlyingConfigurationCodeField;
    // Reusable subtree assignment is not required because child parts are assigned through Configuration/Effectivity.
    // @FXML private TextField subTreeMasterIdField;

    public ConfigurationFormController(ConfigurationViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        if (hierarchicalPositionField != null) {
            hierarchicalPositionField.textProperty().bind(viewModel.hierarchicalPositionProperty());
        }
        bind(sequenceField, viewModel.sequenceProperty());
        LookupFieldBinding.bind(parentHardwarePositionLookupController, "configurations", "Hardware Host", "Search hardware position", viewModel.parentHardwarePositionIdProperty(), viewModel.parentHardwarePositionLookupQueryProperty());
        LookupFieldBinding.bind(approvedPartMasterLookupController, "parts", "Approved Part", "Search approved part", viewModel.approvedPartMasterIdProperty(), viewModel.approvedPartMasterLookupQueryProperty());
        LookupFieldBinding.bind(allowableAlternatePartLookupController, "parts", "Alternate Part", "Search alternate part", viewModel.allowableAlternatePartIdProperty(), viewModel.allowableAlternatePartLookupQueryProperty());
        if (functionalPositionTypeComboBox != null) {
            functionalPositionTypeComboBox.setItems(viewModel.functionalPositionTypes());
            functionalPositionTypeComboBox.valueProperty().bindBidirectional(viewModel.functionalPositionTypeProperty());
        }
        if (equipmentReferenceKeyGroup != null) {
            equipmentReferenceKeyGroup.visibleProperty().bind(Bindings.createBooleanBinding(
                    () -> "Software".equalsIgnoreCase(viewModel.functionalPositionTypeProperty().get()),
                    viewModel.functionalPositionTypeProperty()));
            equipmentReferenceKeyGroup.managedProperty().bind(equipmentReferenceKeyGroup.visibleProperty());
        }
        bind(functionalPositionCodeField, viewModel.functionalPositionCodeProperty());
        if (functionalPositionCodeField != null) {
            functionalPositionCodeField.editableProperty().bind(viewModel.fpGeneratorModeProperty().isEqualTo("Custom"));
            functionalPositionCodeField.focusTraversableProperty().bind(viewModel.fpGeneratorModeProperty().isEqualTo("Custom"));
        }
        if (generateFunctionalPositionCodeButton != null) {
            generateFunctionalPositionCodeButton.disableProperty().bind(
                    viewModel.fpGeneratorModeProperty().isEqualTo("Custom").or(
                            Bindings.createBooleanBinding(
                                    () -> viewModel.functionalPositionNameProperty().get() == null
                                            || viewModel.functionalPositionNameProperty().get().isBlank(),
                                    viewModel.functionalPositionNameProperty())));
        }
        bind(functionalPositionNameField, viewModel.functionalPositionNameProperty());
        bind(functionalPositionDescriptionField, viewModel.functionalPositionDescriptionProperty());
        bind(functionalPartQtyField, viewModel.functionalPartQtyProperty());
        bind(positionLeftHandCheckBox, viewModel.positionLeftHandProperty());
        bind(positionRightHandCheckBox, viewModel.positionRightHandProperty());
        bind(positionForwardCheckBox, viewModel.positionForwardProperty());
        bind(positionAftCheckBox, viewModel.positionAftProperty());
        bind(positionNumberStartField, viewModel.positionNumberStartProperty());
        bind(positionNumberEndField, viewModel.positionNumberEndProperty());
        enforceExclusive(positionLeftHandCheckBox, positionRightHandCheckBox);
        enforceExclusive(positionForwardCheckBox, positionAftCheckBox);
        bind(levelField, viewModel.levelProperty());
        bind(aircraftSystemCodeField, viewModel.aircraftSystemCodeProperty());
        bind(equipmentReferenceKeyField, viewModel.equipmentReferenceKeyProperty());
        bind(effectiveDateField, viewModel.effectiveDateProperty());
        bind(missionConfigurationCodeField, viewModel.missionConfigurationCodeProperty());
        bind(statusReportInformationField, viewModel.statusReportInformationProperty());
        bind(massStorageDeviceCodeField, viewModel.massStorageDeviceCodeProperty());
        bind(rescueKitCodeField, viewModel.rescueKitCodeProperty());
        bind(zoneCodeField, viewModel.zoneCodeProperty());
        bind(panelCodeField, viewModel.panelCodeProperty());
        bind(massStorageDeviceCheckBox, viewModel.massStorageDeviceProperty());
        bind(rescueKitCheckBox, viewModel.rescueKitProperty());
        bind(ipcReferenceField, viewModel.ipcReferenceProperty());
        // bind(changeableReasonField, viewModel.changeableReasonProperty());
        // bind(bookedConfigurationCodeField, viewModel.bookedConfigurationCodeProperty());
        // bind(asFlyingConfigurationCodeField, viewModel.asFlyingConfigurationCodeProperty());
        // bind(subTreeMasterIdField, viewModel.subTreeMasterIdProperty());
        bind(groupCheckBox, viewModel.groupProperty());
        bind(optionalCheckBox, viewModel.optionalProperty());
        // bind(inactiveCheckBox, viewModel.inactiveProperty());
        bind(lruCheckBox, viewModel.lruProperty());
        bind(qecCheckBox, viewModel.qecProperty());
        bind(asFlyingFpCheckBox, viewModel.asFlyingFpProperty());
        bind(informationOnlyCheckBox, viewModel.informationOnlyProperty());
        bind(excludeAsFlyingFpCheckBox, viewModel.excludeAsFlyingFpProperty());
        // bind(mandatoryCheckBox, viewModel.mandatoryProperty());
        // bind(preferredCheckBox, viewModel.preferredProperty());
        // bind(hasRequirementCheckBox, viewModel.hasRequirementProperty());
        // bind(hasSoftRequirementCheckBox, viewModel.hasSoftRequirementProperty());
        // bind(subTreeRootCheckBox, viewModel.subTreeRootProperty());
        // bind(uniqueFpCodeRequiredCheckBox, viewModel.uniqueFpCodeRequiredProperty());
        bind(allowAlternatePartsCheckBox, viewModel.allowAlternatePartsProperty());
        // bind(includeSubConfigurationsCheckBox, viewModel.includeSubConfigurationsProperty());
        // bind(selectedCheckBox, viewModel.selectedProperty());
        // bind(changeableCheckBox, viewModel.changeableProperty());
        if (parentConfigurationLookupController != null) {
            LookupFieldBinding.bind(
                    parentConfigurationLookupController,
                    "configurations",
                    "Parent Configuration",
                    "Search parent configuration",
                    viewModel.parentConfigurationIdProperty(),
                    viewModel.parentConfigurationLookupQueryProperty()
            );
        }
        if (ataLookupController != null) {
            LookupFieldBinding.bind(ataLookupController, "ata", "ATA", "Search ATA", viewModel.ataIdProperty(), viewModel.ataLookupQueryProperty());
        }
        if (ownerBusinessPartnerLookupController != null) {
            LookupFieldBinding.bind(ownerBusinessPartnerLookupController, "business-partners", "Owner Business Partner", "Search owner business partner", viewModel.ownerBusinessPartnerIdProperty(), viewModel.ownerBusinessPartnerLookupQueryProperty());
        }
        if (locationReferenceLookupController != null) {
            LookupFieldBinding.bind(
                    locationReferenceLookupController,
                    "locationreferences",
                    "Physical Location",
                    "Search physical location",
                    viewModel.locationReferenceIdProperty(),
                    viewModel.locationReferenceLookupQueryProperty()
            );
        }
        // if (bookedConfigurationMasterLookupController != null) {
        //     LookupFieldBinding.bind(bookedConfigurationMasterLookupController, "configuration-masters", "Booked Configuration Master", "Search booked configuration master", viewModel.bookedConfigurationMasterIdProperty(), viewModel.bookedConfigurationMasterLookupQueryProperty());
        // }
        // if (asFlyingConfigurationMasterLookupController != null) {
        //     LookupFieldBinding.bind(asFlyingConfigurationMasterLookupController, "configuration-masters", "As-Flying Configuration Master", "Search as-flying configuration master", viewModel.asFlyingConfigurationMasterIdProperty(), viewModel.asFlyingConfigurationMasterLookupQueryProperty());
        // }
    }

    private void bind(TextField field, javafx.beans.property.StringProperty property) {
        if (field != null) {
            field.textProperty().bindBidirectional(property);
        }
    }

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

    private void enforceExclusive(CheckBox first, CheckBox second) {
        if (first == null || second == null) return;
        first.setOnAction(event -> { if (first.isSelected()) second.setSelected(false); });
        second.setOnAction(event -> { if (second.isSelected()) first.setSelected(false); });
    }

    @FXML
    private void generateFunctionalPositionCode() {
        if (viewModel.functionalPositionNameProperty().get() == null
                || viewModel.functionalPositionNameProperty().get().isBlank()) return;
        String mode = viewModel.fpGeneratorModeProperty().get();
        if (mode == null || mode.isBlank() || "Custom".equalsIgnoreCase(mode)) return;
        String code = switch (mode) {
            case "Number" -> "1";
            case "Incremental" -> "FP.1";
            case "PartNumber" -> suffix(viewModel.approvedPartMasterLookupQueryProperty().get(), "FP");
            case "S1000D" -> suffix(viewModel.equipmentReferenceKeyProperty().get(), "FP");
            case "Position", "Automatic", "DefinedByParameter" -> suffix(viewModel.functionalPositionNameProperty().get(), "FP");
            default -> "FP.1";
        };
        viewModel.functionalPositionCodeProperty().set(code);
    }

    private static String suffix(String value, String fallback) {
        String source = value == null ? "" : value.trim().replaceAll("[^A-Za-z0-9_-]+", "-");
        source = source.replaceAll("^-+|-+$", "");
        return (source.isBlank() ? fallback : source) + ".1";
    }
}

Manage Configuration / Remarks

GUI review pending

Configuration 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: Manage Configuration / Remarks
Direct application capture · 2026-10-10T12:30:44.409022 · 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: Manage Configuration / 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: manageconfiguration--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;
    }
}

Manage Configuration / Effectivity

GUI review pending

Open configuration effectivity.

Navigation destination — This tab opens the related workspace. Image shows its actual destination, captured in the same form flow.

Navigation destination: Manage Configuration / Effectivity
Navigation destination · 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: Manage Configuration / Effectivity
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.

This is a navigation shortcut with no independent FXML form. Review its destination, selected-record context and return path.

Control inventory (0)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Source evidence

Tab key: manageconfiguration--effectivity
Section: Entire form
FXML SHA-256: Navigation only

Manage Configuration / Audit

GUI review pending

Configuration 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: Manage Configuration / Audit
Direct application capture · 2026-10-10T12:30:49.015736 · 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: Manage Configuration / 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: manageconfiguration--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;
    }
}

Manage Configuration / 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: Manage Configuration / Revision
Direct application capture · 2026-10-10T12:30:31.866992 · 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: Manage Configuration / 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: manageconfiguration--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

Configuration Effectivity

Configuration Effectivity / Main

GUI review pending

Configuration effectivity 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 Effectivity / Main
Direct application capture · 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: Configuration 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: configurationeffectivity--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);
        }
    }
}

Configuration Effectivity / Maintenance

GUI review pending

Maintenance program, task, and task card links.

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 Effectivity / Maintenance
Direct application capture · 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: Configuration 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: configurationeffectivity--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);
        }
    }
}

Configuration Effectivity / Deferred Maintenance

GUI review pending

Deferred maintenance link.

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 Effectivity / Deferred Maintenance
Direct application capture · 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: Configuration 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: configurationeffectivity--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);
        }
    }
}

Configuration Effectivity / Publication

GUI review pending

Publication link.

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 Effectivity / Publication
Direct application capture · 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: Configuration 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: configurationeffectivity--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);
        }
    }
}

Configuration Effectivity / Configuration

GUI review pending

Configuration master and configuration links.

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 Effectivity / Configuration
Direct application capture · 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: Configuration 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: configurationeffectivity--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);
        }
    }
}

Configuration Effectivity / Part

GUI review pending

Part master and BOM links.

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 Effectivity / Part
Direct application capture · 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: Configuration 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: configurationeffectivity--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);
        }
    }
}

Configuration Effectivity / Rotable

GUI review pending

Rotable link.

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 Effectivity / Rotable
Direct application capture · 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: Configuration 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: configurationeffectivity--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);
        }
    }
}

Configuration Effectivity / Operations

GUI review pending

Operator and operation specification link.

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 Effectivity / Operations
Direct application capture · 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: Configuration 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: configurationeffectivity--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);
        }
    }
}

Configuration Effectivity / Validity

GUI review pending

Configuration effectivity validity dates.

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 Effectivity / Validity
Direct application capture · 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: Configuration 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: configurationeffectivity--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);
        }
    }
}

Configuration Effectivity / Remarks

GUI review pending

Configuration effectivity 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 Effectivity / Remarks
Direct application capture · 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: Configuration 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: configurationeffectivity--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 Effectivity / Audit

GUI review pending

Configuration effectivity 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 Effectivity / Audit
Direct application capture · 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: Configuration 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: configurationeffectivity--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 Tree

Configuration Tree / Tree

GUI review pending

View the dated allowed-configuration hierarchy.

Source layout only: Declared nested workspace is not exposed by the current empty-record navigation. Live capture requires an available entry path or saved record. Static layout included.

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 Tree / Tree
Static JavaFX layout · 1280 × 692 · 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
Refresh Tree
Refresh Tree
Button
Static control
onAction=#refresh
Configuration Tree
tree
TreeView
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source evidence

Tab key: configurationtree--tree
Section: Entire form
FXML SHA-256: 790b4e136615c0516fd23e672c0a9884fb7fc4a39362a7167c9d960ba79f6be9

coflyr-fe/src/main/resources/com/coflyr/fe/configuration/views/configuration-tree-form.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="root" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.configuration.controllers.ConfigurationTreeController"
      spacing="8" fillWidth="true" maxWidth="Infinity" styleClass="standard-section-flat">
    <Label text="Configuration Tree" styleClass="section-title"/>
    <Label text="Functional positions and dated sub-configuration allowances. Conditions and approval must be evaluated separately." wrapText="true" styleClass="field-hint"/>
    <Button text="Refresh Tree" onAction="#refresh"/>
    <TreeView fx:id="tree" prefHeight="480" minHeight="240" VBox.vgrow="ALWAYS"/>
    <Label fx:id="status" wrapText="true"/>
</VBox>
Controller: com.coflyr.fe.configuration.controllers.ConfigurationTreeController
package com.coflyr.fe.configuration.controllers;

import com.coflyr.fe.configuration.services.ConfigurationMasterService;
import com.coflyr.fe.configuration.viewmodels.ConfigurationMasterViewModel;
import com.coflyr.fe.config.FxmlLoaderFactory;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.VBox;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

@Component
@Scope("prototype")
public final class ConfigurationTreeController {
    private final ConfigurationMasterViewModel master;
    private final ConfigurationMasterService service;
    private final Executor executor;
    private long requestVersion;
    private boolean destroyed;
    @FXML private VBox root;
    @FXML private TreeView<String> tree;
    @FXML private Label status;

    public ConfigurationTreeController(ConfigurationMasterViewModel master,
            ConfigurationMasterService service, @Qualifier("formCommandExecutor") Executor executor) {
        this.master = master; this.service = service; this.executor = executor;
    }
    @FXML private void initialize() {
        ChangeListener<String> listener = (obs, before, after) -> refresh();
        master.selectedConfigurationMasterIdProperty().addListener(listener);
        FxmlLoaderFactory.onDestroy(root, () -> {
            destroyed = true; requestVersion++;
            master.selectedConfigurationMasterIdProperty().removeListener(listener);
        });
        refresh();
    }
    @FXML private void refresh() {
        long version = ++requestVersion;
        String id = master.selectedConfigurationMasterIdProperty().get();
        tree.setRoot(null);
        if (id == null || id.isBlank()) { status.setText("Select a saved Configuration Master."); return; }
        status.setText("Loading configuration tree...");
        CompletableFuture.supplyAsync(() -> {
            try { return service.tree(id); }
            catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new java.util.concurrent.CompletionException(e); }
            catch (Exception e) { throw new java.util.concurrent.CompletionException(e); }
        }, executor).whenComplete((data, error) -> Platform.runLater(() -> {
            if (destroyed || version != requestVersion || !id.equals(master.selectedConfigurationMasterIdProperty().get())) return;
            if (error != null) { status.setText("Tree could not be loaded. Check the API log and retry."); return; }
            try {
                var item = render(data, 0); item.setExpanded(true); tree.setRoot(item);
                status.setText("Current-date definition tree. This is not an installed-configuration compliance result.");
            } catch (RuntimeException e) { status.setText("Invalid tree response. Check the backend hierarchy."); }
        }));
    }
    // Render the nested server DTO directly; no extra lookups or relationship joins.
    static TreeItem<String> render(Object data, int depth) {
        if (depth > 150 || !(data instanceof Map<?, ?> map)) throw new IllegalArgumentException("Invalid tree node");
        String label = value(map,"code") + " - " + value(map,"name") + " [" + value(map,"kind") + ", qty " + value(map,"quantity") + "]";
        if (Boolean.TRUE.equals(map.get("expansionLimited"))) label += " (not expanded)";
        if (!value(map,"rule").isBlank()) label += " - Conditional: " + value(map,"rule");
        var item = new TreeItem<>(label);
        if (map.get("children") instanceof List<?> children)
            for (Object child : children) item.getChildren().add(render(child, depth + 1));
        return item;
    }
    private static String value(Map<?, ?> map, String key) { return map.get(key) == null ? "" : String.valueOf(map.get(key)); }
}

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
FunctionalPositionName
Records functional position name for this configuration.
string · RequiredFunctional Position / Functional Position Name
EquipmentReferenceKey
Records equipment reference key for this configuration.
string · NullableFunctional Position / Equipment Reference Key (Software/Boeing)
IpcReference
Records ipc reference for this configuration.
string · NullableIPC & Existing References / IPC Reference
AllowAlternateParts
Indicates whether allow alternate parts applies to this configuration.
bool · RequiredHierarchy & Options / Allow Alternate Parts
ExcludeAsFlyingFp
Indicates whether exclude as flying fp applies to this configuration.
bool · RequiredHierarchy & Options / Exclude As-Flying FP
InformationOnly
Indicates whether information only applies to this configuration.
bool · RequiredHierarchy & Options / Information Only
IsAsFlyingFp
Indicates whether as flying fp applies to this configuration.
bool · RequiredNo direct binding match — review mapping or deliberate omission
IsGroup
Indicates whether group applies to this configuration.
bool · RequiredNo direct binding match — review mapping or deliberate omission
IsLru
Indicates whether lru applies to this configuration.
bool · RequiredNo direct binding match — review mapping or deliberate omission
IsMassStorageDevice
Indicates whether mass storage device applies to this configuration.
bool · RequiredNo direct binding match — review mapping or deliberate omission
IsOptional
Indicates whether optional applies to this configuration.
bool · RequiredNo direct binding match — review mapping or deliberate omission
IsQec
Indicates whether qec applies to this configuration.
bool · RequiredNo direct binding match — review mapping or deliberate omission
IsRescueKit
Indicates whether rescue kit applies to this configuration.
bool · RequiredNo direct binding match — review mapping or deliberate omission
IsUniqueFpCodeRequired
Indicates whether unique fp code required applies to this configuration.
bool · RequiredNo direct binding match — review mapping or deliberate omission
PositionAft
Indicates whether position aft applies to this configuration.
bool · RequiredDirection & Numbering / AFT (Aft)
PositionForward
Indicates whether position forward applies to this configuration.
bool · RequiredDirection & Numbering / FWD (Forward)
PositionLeftHand
Indicates whether position left hand applies to this configuration.
bool · RequiredDirection & Numbering / LH (Left-hand)
PositionRightHand
Indicates whether position right hand applies to this configuration.
bool · RequiredDirection & Numbering / RH (Right-hand)
StatusReportInformation
Records status report information for this configuration.
string · NullableNo direct binding match — review mapping or deliberate omission
PositionNumberEnd
Records position number end for this configuration.
int? · NullableDirection & Numbering / Number End
PositionNumberStart
Records position number start for this configuration.
int? · NullableDirection & Numbering / Number Start
FunctionalPositionDescription
Records functional position description for this configuration.
string · RequiredFunctional Position / Functional Position Description
SystemAuditId
Links to system audit history when this model stores the reference.
Guid? · NullableNo direct binding match — review mapping or deliberate omission
AircraftSystemCode
Records aircraft system code for this configuration.
string · NullableNo direct binding match — review mapping or deliberate omission
FunctionalPartQty
Records functional part qty for this configuration.
decimal · RequiredFunctional Position / Functional Part Qty
FunctionalPositionCode
Records functional position code for this configuration.
string · RequiredFunctional Position / Functional Position Code *
FunctionalPositionType
Records functional position type for this configuration.
int · RequiredFunctional Position / Functional Position Type
Level
Records level for this configuration.
int · RequiredFunctional Position / Level
MassStorageDeviceCode
Records mass storage device code for this configuration.
string · NullableNo direct binding match — review mapping or deliberate omission
MissionConfigurationCode
Records mission configuration code for this configuration.
string · NullableNo direct binding match — review mapping or deliberate omission
PanelCode
Records panel code for this configuration.
string · NullableIPC & Existing References / Panel
RescueKitCode
Records rescue kit code for this configuration.
string · NullableNo direct binding match — review mapping or deliberate omission
Sequence
Records sequence for this configuration.
int · RequiredDirection & Numbering / Sequence
ZoneCode
Records zone code for this configuration.
string · NullableIPC & Existing References / Zone

Runtime review checklist

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