← Frontend reviews · All model classes

Coflyr · Frontend form review · 10 October 2026

PartMaster

Part identity, engineering, logistics and related workspaces

Backend model: ReviewedGUI: Pending review8 workspaces · 57 tabs346 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 PartMaster 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

Part

Part / Main

GUI review pending

Main 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: Part / Main
Direct application capture · 2026-10-10T12:23:05.029973 · 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: Part / Main
Static JavaFX layout · 1280 × 420 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (10)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Part Number *
partNumberField
TextField
partNumberProperty
Review requiredness, validation and display.
Part Name *
partNameField
TextField
partNameProperty
Review requiredness, validation and display.
Special Code
partSpecialCodeField
TextField
partSpecialCodeProperty
Review requiredness, validation and display.
Applicability Scope
applicabilityScopeComboBox
ComboBox
applicabilityScopeProperty
Review requiredness, validation and display.
Material Class
materialClassComboBox
ComboBox
materialClassProperty
Review requiredness, validation and display.
Airworthiness Critical
airworthinessCriticalCheckBox
CheckBox
airworthinessCriticalProperty
Review requiredness, validation and display.
Repairable
repairableCheckBox
CheckBox
repairableProperty
Review requiredness, validation and display.
PMA
pmaCheckBox
CheckBox
pmaProperty
Review requiredness, validation and display.
Fixed Asset
fixedAssetCheckBox
CheckBox
fixedAssetProperty
Review requiredness, validation and display.
ATA
ataLookup
UUID search lookup
ataIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Source evidence

Tab key: part--main
Section: Entire form
FXML SHA-256: be8e5af04ef9891e301129201490c3d98010f4c3359768e9b44acd170594425d

coflyr-fe/src/main/resources/com/coflyr/fe/part/views/part-form.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/21"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.part.controllers.PartFormController"
      fillWidth="true"
      maxHeight="Infinity"
      maxWidth="Infinity"
      minHeight="0.0"
      spacing="8"
      styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Core Identification"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0">
                    <children><Label styleClass="field-label" text="Part Number *"/><TextField fx:id="partNumberField" 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="Part Name *"/><TextField fx:id="partNameField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children>
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="1">
                    <children><Label styleClass="field-label" text="Special Code"/><TextField fx:id="partSpecialCodeField" 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="Classification &amp; Attributes"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0"><children><Label styleClass="field-label" text="Applicability Scope"/><ComboBox fx:id="applicabilityScopeComboBox" maxWidth="Infinity" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1"><children><Label styleClass="field-label" text="Material Class"/><ComboBox fx:id="materialClassComboBox" maxWidth="Infinity" styleClass="text-input"/></children></VBox>
                <FlowPane hgap="12" vgap="6" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="1"><children><CheckBox fx:id="airworthinessCriticalCheckBox" text="Airworthiness Critical"/><CheckBox fx:id="repairableCheckBox" text="Repairable"/><CheckBox fx:id="pmaCheckBox" text="PMA"/><CheckBox fx:id="fixedAssetCheckBox" text="Fixed Asset"/></children></FlowPane>
            </children>
            <columnConstraints><ColumnConstraints percentWidth="33.333"/><ColumnConstraints percentWidth="33.333"/><ColumnConstraints percentWidth="33.334"/></columnConstraints>
        </GridPane>
        <Label styleClass="section-title" text="References"/>
        <fx:include fx:id="ataLookup" source="../../views/controls/db-search-lookup-field.fxml"/>
    </children>
</VBox>
Controller: com.coflyr.fe.part.controllers.PartFormController
package com.coflyr.fe.part.controllers;

import com.coflyr.fe.part.viewmodels.PartViewModel;
import com.coflyr.fe.part.models.PartOptions;
import javafx.collections.FXCollections;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.control.ComboBox;
import javafx.scene.control.CheckBox;
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 PartFormController {
    private final PartViewModel viewModel;

    @FXML private TextField partNumberField;
    @FXML private TextField partNameField;
    @FXML private DbSearchLookupFieldController ataLookupController;
    @FXML private TextField partSpecialCodeField;
    @FXML private ComboBox<String> applicabilityScopeComboBox;
    @FXML private ComboBox<String> materialClassComboBox;
    @FXML private CheckBox airworthinessCriticalCheckBox, repairableCheckBox, pmaCheckBox, fixedAssetCheckBox;

    public PartFormController(PartViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        partNumberField.textProperty().bindBidirectional(viewModel.partNumberProperty());
        partNameField.textProperty().bindBidirectional(viewModel.partNameProperty());
        partSpecialCodeField.textProperty().bindBidirectional(viewModel.partSpecialCodeProperty());
        applicabilityScopeComboBox.setItems(FXCollections.observableArrayList(PartOptions.APPLICABILITY_SCOPES));
        applicabilityScopeComboBox.valueProperty().bindBidirectional(viewModel.applicabilityScopeProperty());
        materialClassComboBox.setItems(FXCollections.observableArrayList(PartOptions.MATERIAL_CLASSES));
        materialClassComboBox.valueProperty().bindBidirectional(viewModel.materialClassProperty());
        airworthinessCriticalCheckBox.selectedProperty().bindBidirectional(viewModel.airworthinessCriticalProperty());
        repairableCheckBox.selectedProperty().bindBidirectional(viewModel.repairableProperty());
        pmaCheckBox.selectedProperty().bindBidirectional(viewModel.pmaProperty());
        fixedAssetCheckBox.selectedProperty().bindBidirectional(viewModel.fixedAssetProperty());
        // Fixed Asset Type, Measurement Unit, Material Type and Manufacturer
        // moved to the
        // Links tab (PartClassificationLinksFormController): they are links to
        // master-data records, not attributes of the part.
        LookupFieldBinding.bind(ataLookupController, "ata", "ATA", "Search ATA", viewModel.ataIdProperty(), viewModel.ataLookupQueryProperty());
    }
}

Part / Engineering & Logistics

GUI review pending

Engineering & Logistics 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: Part / Engineering & Logistics
Direct application capture · 2026-10-10T12:24:29.750303 · 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: Part / Engineering & Logistics
Static JavaFX layout · 1280 × 564 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (10)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Drawing Number
drawingNumberField
TextField
drawingNumberProperty
Review requiredness, validation and display.
Drawing Revision
drawingRevisionField
TextField
drawingRevisionProperty
Review requiredness, validation and display.
Specification
specificationArea
TextArea
specificationProperty
Review requiredness, validation and display.
Shelf Life Days
shelfLifeDaysField
TextField
shelfLifeDaysProperty
Review requiredness, validation and display.
Minimum Shelf Life Days
minimumShelfLifeDaysField
TextField
minimumShelfLifeDaysProperty
Review requiredness, validation and display.
Max Shop Visits
maxShopVisitsField
TextField
maxShopVisitsProperty
Review requiredness, validation and display.
Shop Visit Reset Condition
shopVisitResetConditionArea
TextArea
shopVisitResetConditionProperty
Review requiredness, validation and display.
Store Time
storeTimeField
TextField
storeTimeProperty
Review requiredness, validation and display.
Store Time Days
storeTimeDaysField
TextField
storeTimeDaysProperty
Review requiredness, validation and display.
Weight
weightField
TextField
weightProperty
Review requiredness, validation and display.
Source evidence

Tab key: part--specification
Section: Entire form
FXML SHA-256: 4cec2b7c98cbe0c392878c00def3f3dc513eec960c69ffd2b718ff475b3c9e63

coflyr-fe/src/main/resources/com/coflyr/fe/part/views/part-specification-form.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?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.part.controllers.PartSpecificationController"
      fillWidth="true" maxHeight="Infinity" maxWidth="Infinity" minHeight="0.0" spacing="8" styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Engineering"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0"><children><Label styleClass="field-label" text="Drawing Number"/><TextField fx:id="drawingNumberField" 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="Drawing Revision"/><TextField fx:id="drawingRevisionField" 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 styleClass="field-label" text="Specification"/><TextArea fx:id="specificationArea" maxWidth="Infinity" minHeight="56.0" prefHeight="64.0" prefRowCount="2" styleClass="form-text-area" wrapText="true"/></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="Shelf Life"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0"><children><Label styleClass="field-label" text="Shelf Life Days"/><TextField fx:id="shelfLifeDaysField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1"><children><Label styleClass="field-label" text="Minimum Shelf Life Days"/><TextField fx:id="minimumShelfLifeDaysField" 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="Shop Visits"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0"><children><Label styleClass="field-label" text="Max Shop Visits"/><TextField fx:id="maxShopVisitsField" 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 styleClass="field-label" text="Shop Visit Reset Condition"/><TextArea fx:id="shopVisitResetConditionArea" maxWidth="Infinity" minHeight="56.0" prefHeight="64.0" prefRowCount="2" styleClass="form-text-area" wrapText="true"/></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="Storage &amp; Logistics"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0"><children><Label styleClass="field-label" text="Store Time"/><TextField fx:id="storeTimeField" maxWidth="Infinity" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1"><children><Label styleClass="field-label" text="Store Time Days"/><TextField fx:id="storeTimeDaysField" maxWidth="Infinity" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2"><children><Label styleClass="field-label" text="Weight"/><TextField fx:id="weightField" maxWidth="Infinity" styleClass="text-input"/></children></VBox>
            </children>
            <columnConstraints><ColumnConstraints percentWidth="33.333"/><ColumnConstraints percentWidth="33.333"/><ColumnConstraints percentWidth="33.334"/></columnConstraints>
        </GridPane>
    </children>
</VBox>
Controller: com.coflyr.fe.part.controllers.PartSpecificationController
package com.coflyr.fe.part.controllers;

import com.coflyr.fe.part.viewmodels.PartViewModel;
import javafx.fxml.FXML;
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;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PartSpecificationController {
    private final PartViewModel viewModel;

    @FXML private TextField drawingNumberField;
    @FXML private TextField drawingRevisionField;
    @FXML private TextField shelfLifeDaysField;
    @FXML private TextField minimumShelfLifeDaysField;
    @FXML private TextField maxShopVisitsField;
    @FXML private TextArea shopVisitResetConditionArea;
    @FXML private TextField storeTimeField;
    @FXML private TextField storeTimeDaysField;
    @FXML private TextField weightField;
    @FXML private TextArea specificationArea;

    public PartSpecificationController(PartViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        drawingNumberField.textProperty().bindBidirectional(viewModel.drawingNumberProperty());
        drawingRevisionField.textProperty().bindBidirectional(viewModel.drawingRevisionProperty());
        shelfLifeDaysField.textProperty().bindBidirectional(viewModel.shelfLifeDaysProperty());
        minimumShelfLifeDaysField.textProperty().bindBidirectional(viewModel.minimumShelfLifeDaysProperty());
        maxShopVisitsField.textProperty().bindBidirectional(viewModel.maxShopVisitsProperty());
        shopVisitResetConditionArea.textProperty().bindBidirectional(viewModel.shopVisitResetConditionProperty());
        storeTimeField.textProperty().bindBidirectional(viewModel.storeTimeProperty());
        storeTimeDaysField.textProperty().bindBidirectional(viewModel.storeTimeDaysProperty());
        weightField.textProperty().bindBidirectional(viewModel.weightProperty());
        specificationArea.textProperty().bindBidirectional(viewModel.specificationProperty());
    }
}

Part / Remarks

GUI review pending

Remarks 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: Part / Remarks
Direct application capture · 2026-10-10T12:24:33.150330 · 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: Part / 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: part--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;
    }
}

Part / Audit

GUI review pending

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: Part / Audit
Direct application capture · 2026-10-10T12:24:59.657151 · 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: Part / 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: part--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;
    }
}

Part / 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: Part / Revision
Direct application capture · 2026-10-10T12:24:55.547280 · 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: Part / 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: part--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;
    }
}

Financial (Under Development)

Financial (Under Development) / Financial

GUI review pending

Financial details.

Source layout only: Disabled in the running application: this workspace is labelled Under Development.

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: Financial (Under Development) / Financial
Static JavaFX layout · 1280 × 284 · 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
Financial Quantity
financialQuantityField
TextField
financialQuantityProperty
Review requiredness, validation and display.
Purchase Price
purchasePriceField
TextField
purchasePriceProperty
Review requiredness, validation and display.
Sales Price
salesPriceField
TextField
salesPriceProperty
Review requiredness, validation and display.
Average Price Local
averagePriceLocalField
TextField
averagePriceLocalProperty
Review requiredness, validation and display.
Average Price Market
averagePriceMarketField
TextField
averagePriceMarketProperty
Review requiredness, validation and display.
Average Repair Cost
averageRepairCostField
TextField
averageRepairCostProperty
Review requiredness, validation and display.
Financial Entity
financialEntityField
TextField
financialEntityProperty
Review requiredness, validation and display.
Source evidence

Tab key: partfinancial--financial
Section: Entire form
FXML SHA-256: e6e8cceed22ec28aa4dccbdeb7263202e8df6439349b93f80bd859883c721cbb

coflyr-fe/src/main/resources/com/coflyr/fe/part/views/part-financial-form.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?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.part.controllers.PartFinancialController"
      fillWidth="true" maxHeight="Infinity" maxWidth="Infinity" minHeight="0.0" spacing="8" styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Financial Values"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0"><children><Label styleClass="field-label" text="Financial Quantity"/><TextField fx:id="financialQuantityField" 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="Purchase Price"/><TextField fx:id="purchasePriceField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="0"><children><Label styleClass="field-label" text="Sales Price"/><TextField fx:id="salesPriceField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="1"><children><Label styleClass="field-label" text="Average Price Local"/><TextField fx:id="averagePriceLocalField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="1"><children><Label styleClass="field-label" text="Average Price Market"/><TextField fx:id="averagePriceMarketField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="1"><children><Label styleClass="field-label" text="Average Repair Cost"/><TextField fx:id="averageRepairCostField" 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="Financial Entity"/><TextField fx:id="financialEntityField" 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.part.controllers.PartFinancialController
package com.coflyr.fe.part.controllers;

import com.coflyr.fe.part.viewmodels.PartViewModel;
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 PartFinancialController {
    private final PartViewModel viewModel;

    @FXML private TextField financialQuantityField;
    @FXML private TextField purchasePriceField;
    @FXML private TextField salesPriceField;
    @FXML private TextField averagePriceLocalField;
    @FXML private TextField averagePriceMarketField;
    @FXML private TextField averageRepairCostField;
    @FXML private TextField financialEntityField;

    public PartFinancialController(PartViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        financialQuantityField.textProperty().bindBidirectional(viewModel.financialQuantityProperty());
        purchasePriceField.textProperty().bindBidirectional(viewModel.purchasePriceProperty());
        salesPriceField.textProperty().bindBidirectional(viewModel.salesPriceProperty());
        averagePriceLocalField.textProperty().bindBidirectional(viewModel.averagePriceLocalProperty());
        averagePriceMarketField.textProperty().bindBidirectional(viewModel.averagePriceMarketProperty());
        averageRepairCostField.textProperty().bindBidirectional(viewModel.averageRepairCostProperty());
        financialEntityField.textProperty().bindBidirectional(viewModel.financialEntityProperty());
    }
}

Financial (Under Development) / Customs

GUI review pending

Customs details.

Source layout only: Disabled in the running application: this workspace is labelled Under Development.

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: Financial (Under Development) / Customs
Static JavaFX layout · 1280 × 422 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (8)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Commodity Code
commodityCodeLookup
UUID search lookup
commodityCodeIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Classification
classificationField
TextField
classificationProperty
Review requiredness, validation and display.
End Use
endUseLookup
UUID search lookup
endUseIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
End Use Valid Until
endUseValidUntilField
CustomDateTimeField
endUseValidUntilProperty
promptText=yyyy-MM-dd
Currency
currencyLookup
UUID search lookup
currencyIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Value
customsValueField
TextField
customsValueProperty
Review requiredness, validation and display.
Measurement Unit
customsMeasurementUnitLookup
UUID search lookup
customsMeasurementUnitIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Customs Description
customsDescriptionArea
TextArea
customsDescriptionProperty
Review requiredness, validation and display.
Source evidence

Tab key: partfinancial--customs
Section: Entire form
FXML SHA-256: fe5d9c0e1d07c7d9c4f41b987470928d1a8bf2ca5d1bc3d77e8231fbfc4ceb73

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

<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?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.part.controllers.PartCustomsController"
      fillWidth="true" maxHeight="Infinity" maxWidth="Infinity" minHeight="0.0" spacing="8" styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Customs"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0"><children><fx:include fx:id="commodityCodeLookup" source="../../views/controls/db-search-lookup-field.fxml"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="0"><children><Label styleClass="field-label" text="Classification"/><TextField fx:id="classificationField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="0"><children><fx:include fx:id="endUseLookup" source="../../views/controls/db-search-lookup-field.fxml"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="1"><children><Label styleClass="field-label" text="End Use Valid Until"/><CustomDateTimeField fx:id="endUseValidUntilField" maxWidth="Infinity" prefWidth="1.0" promptText="yyyy-MM-dd" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="1"><children><fx:include fx:id="currencyLookup" source="../../views/controls/db-search-lookup-field.fxml"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="1"><children><Label styleClass="field-label" text="Value"/><TextField fx:id="customsValueField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="2"><children><fx:include fx:id="customsMeasurementUnitLookup" source="../../views/controls/db-search-lookup-field.fxml"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="3"><children><Label styleClass="field-label" text="Customs Description"/><TextArea fx:id="customsDescriptionArea" maxWidth="Infinity" minHeight="56.0" prefHeight="64.0" prefRowCount="2" styleClass="form-text-area" wrapText="true"/></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.part.controllers.PartCustomsController
package com.coflyr.fe.part.controllers;

import com.coflyr.fe.part.viewmodels.PartViewModel;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.fxml.FXML;
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;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PartCustomsController {
    private final PartViewModel viewModel;

    @FXML private DbSearchLookupFieldController commodityCodeLookupController;
    @FXML private TextField classificationField;
    @FXML private DbSearchLookupFieldController endUseLookupController;
    @FXML private TextField endUseValidUntilField;
    @FXML private TextArea customsDescriptionArea;
    @FXML private DbSearchLookupFieldController currencyLookupController;
    @FXML private TextField customsValueField;
    @FXML private DbSearchLookupFieldController customsMeasurementUnitLookupController;

    public PartCustomsController(PartViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        LookupFieldBinding.bind(commodityCodeLookupController, "commoditycode", "Commodity Code", "Search commodity code", viewModel.commodityCodeIdProperty(), viewModel.commodityCodeLookupQueryProperty());
        classificationField.textProperty().bindBidirectional(viewModel.classificationProperty());
        LookupFieldBinding.bind(endUseLookupController, "customsenduse", "End Use", "Search customs end use", viewModel.endUseIdProperty(), viewModel.endUseLookupQueryProperty());
        endUseValidUntilField.textProperty().bindBidirectional(viewModel.endUseValidUntilProperty());
        customsDescriptionArea.textProperty().bindBidirectional(viewModel.customsDescriptionProperty());
        LookupFieldBinding.bind(currencyLookupController, "currency", "Currency", "Search currency", viewModel.currencyIdProperty(), viewModel.currencyLookupQueryProperty());
        customsValueField.textProperty().bindBidirectional(viewModel.customsValueProperty());
        LookupFieldBinding.bind(customsMeasurementUnitLookupController, "measureunit", "Measurement Unit", "Search measure unit", viewModel.customsMeasurementUnitIdProperty(), viewModel.customsMeasurementUnitLookupQueryProperty());
    }
}

Financial (Under Development) / Reorder

GUI review pending

Reorder details.

Source layout only: Disabled in the running application: this workspace is labelled Under Development.

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: Financial (Under Development) / Reorder
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
Activate Reordering
activateReorderingCheckBox
CheckBox
activateReorderingProperty
Review requiredness, validation and display.
Calculate Historical Forecast
calculateHistoricalForecastCheckBox
CheckBox
calculateHistoricalForecastProperty
Review requiredness, validation and display.
Calculate Reorder Level
calculateReorderLevelCheckBox
CheckBox
calculateReorderLevelProperty
Review requiredness, validation and display.
Calculate Safety Stock
calculateSafetyStockCheckBox
CheckBox
calculateSafetyStockProperty
Review requiredness, validation and display.
Individualize by Entity
individualizeReorderingValuesByEntityCheckBox
CheckBox
individualizeReorderingValuesByEntityProperty
Review requiredness, validation and display.
Ideal Quantity
idealQuantityField
TextField
idealQuantityProperty
Review requiredness, validation and display.
Reorder Level
reorderLevelField
TextField
reorderLevelProperty
Review requiredness, validation and display.
Alert Level
alertLevelField
TextField
alertLevelProperty
Review requiredness, validation and display.
Minimum Safety Stock
minimumSafetyStockField
TextField
minimumSafetyStockProperty
Review requiredness, validation and display.
Source evidence

Tab key: partfinancial--reorder
Section: Entire form
FXML SHA-256: e81b2e447118ca1041178a43736e18e1565dd0a08594e1a6bf55a26b03b069a9

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

<?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.VBox?>

<VBox xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.part.controllers.PartReorderController"
      fillWidth="true" maxHeight="Infinity" maxWidth="Infinity" minHeight="0.0" spacing="8" styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="1 Reordering"/>
        <FlowPane hgap="12" vgap="8">
            <children>
                <CheckBox fx:id="activateReorderingCheckBox" styleClass="form-check-box" text="Activate Reordering"/>
                <CheckBox fx:id="calculateHistoricalForecastCheckBox" styleClass="form-check-box" text="Calculate Historical Forecast"/>
                <CheckBox fx:id="calculateReorderLevelCheckBox" styleClass="form-check-box" text="Calculate Reorder Level"/>
                <CheckBox fx:id="calculateSafetyStockCheckBox" styleClass="form-check-box" text="Calculate Safety Stock"/>
                <CheckBox fx:id="individualizeReorderingValuesByEntityCheckBox" styleClass="form-check-box" text="Individualize by Entity"/>
            </children>
        </FlowPane>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0"><children><Label styleClass="field-label" text="Ideal Quantity"/><TextField fx:id="idealQuantityField" 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="Reorder Level"/><TextField fx:id="reorderLevelField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="0"><children><Label styleClass="field-label" text="Alert Level"/><TextField fx:id="alertLevelField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="1"><children><Label styleClass="field-label" text="Minimum Safety Stock"/><TextField fx:id="minimumSafetyStockField" 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.part.controllers.PartReorderController
package com.coflyr.fe.part.controllers;

import com.coflyr.fe.part.viewmodels.PartViewModel;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
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 PartReorderController {
    private final PartViewModel viewModel;

    @FXML private CheckBox activateReorderingCheckBox;
    @FXML private CheckBox calculateHistoricalForecastCheckBox;
    @FXML private CheckBox calculateReorderLevelCheckBox;
    @FXML private CheckBox calculateSafetyStockCheckBox;
    @FXML private CheckBox individualizeReorderingValuesByEntityCheckBox;
    @FXML private TextField idealQuantityField;
    @FXML private TextField reorderLevelField;
    @FXML private TextField alertLevelField;
    @FXML private TextField minimumSafetyStockField;

    public PartReorderController(PartViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        activateReorderingCheckBox.selectedProperty().bindBidirectional(viewModel.activateReorderingProperty());
        calculateHistoricalForecastCheckBox.selectedProperty().bindBidirectional(viewModel.calculateHistoricalForecastProperty());
        calculateReorderLevelCheckBox.selectedProperty().bindBidirectional(viewModel.calculateReorderLevelProperty());
        calculateSafetyStockCheckBox.selectedProperty().bindBidirectional(viewModel.calculateSafetyStockProperty());
        individualizeReorderingValuesByEntityCheckBox.selectedProperty().bindBidirectional(viewModel.individualizeReorderingValuesByEntityProperty());
        idealQuantityField.textProperty().bindBidirectional(viewModel.idealQuantityProperty());
        reorderLevelField.textProperty().bindBidirectional(viewModel.reorderLevelProperty());
        alertLevelField.textProperty().bindBidirectional(viewModel.alertLevelProperty());
        minimumSafetyStockField.textProperty().bindBidirectional(viewModel.minimumSafetyStockProperty());
    }
}

Financial (Under Development) / Remarks (Under Development)

GUI review pending

Remarks (Under Development) details.

Source layout only: Disabled in the running application: this workspace is labelled Under Development.

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: Financial (Under Development) / Remarks (Under Development)
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: partfinancial--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;
    }
}

Dangerous Goods (Under Development)

Dangerous Goods (Under Development) / Details

GUI review pending

Details details.

Source layout only: Disabled in the running application: this workspace is labelled Under Development.

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: Dangerous Goods (Under Development) / Details
Static JavaFX layout · 1280 × 412 · 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
UN Number *
unNumberLookup
UUID search lookup
unNumberIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Hazard Class *
hazardClassLookup
UUID search lookup
hazardClassIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Packing Group *
packingGroupLookup
UUID search lookup
packingGroupIdProperty
Saves the selected record ID; review display label, search fields and relationship cardinality.
Hazardous
hazardousCheckBox
CheckBox
hazardousProperty
Review requiredness, validation and display.
Proper Shipping Name
properShippingNameField
TextField
properShippingNameProperty
Review requiredness, validation and display.
Dangerous Goods Description
dangerousGoodsDescriptionArea
TextArea
dangerousGoodsDescriptionProperty
Review requiredness, validation and display.
Compatibility Group
compatibilityGroupField
TextField
compatibilityGroupProperty
Review requiredness, validation and display.
Source evidence

Tab key: partdangerousgoods--details
Section: Entire form
FXML SHA-256: 7ad53600d0ce2b31d894c7e21cf869c65e579995a0435c7ffe4bb0046e8d7e15

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

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

<VBox xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.coflyr.fe.part.controllers.PartDangerousGoodsController"
      fillWidth="true" maxHeight="Infinity" maxWidth="Infinity" minHeight="0.0" spacing="8" styleClass="standard-section-flat">
    <children>
        <Label styleClass="section-title" text="Dangerous Goods"/>
        <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
            <children>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0"><children><fx:include fx:id="unNumberLookup" source="../../views/controls/db-search-lookup-field.fxml"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="0"><children><fx:include fx:id="hazardClassLookup" source="../../views/controls/db-search-lookup-field.fxml"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="0"><children><fx:include fx:id="packingGroupLookup" source="../../views/controls/db-search-lookup-field.fxml"/></children></VBox>
                <FlowPane hgap="12" maxWidth="Infinity" styleClass="checkbox-field-group form-checkbox-pane" vgap="6" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="1">
                    <children><CheckBox fx:id="hazardousCheckBox" styleClass="form-check-box" text="Hazardous"/></children>
                </FlowPane>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="2"><children><Label styleClass="field-label" text="Proper Shipping Name"/><TextField fx:id="properShippingNameField" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="3"><children><Label styleClass="field-label" text="Dangerous Goods Description"/><TextArea fx:id="dangerousGoodsDescriptionArea" maxWidth="Infinity" minHeight="56.0" prefHeight="64.0" prefRowCount="2" styleClass="form-text-area" wrapText="true"/></children></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="4"><children><Label styleClass="field-label" text="Compatibility Group"/><TextField fx:id="compatibilityGroupField" 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.part.controllers.PartDangerousGoodsController
package com.coflyr.fe.part.controllers;

import com.coflyr.fe.part.viewmodels.PartViewModel;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
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;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PartDangerousGoodsController {
    private final PartViewModel viewModel;

    @FXML private CheckBox hazardousCheckBox;
    @FXML private DbSearchLookupFieldController unNumberLookupController;
    @FXML private DbSearchLookupFieldController hazardClassLookupController;
    @FXML private DbSearchLookupFieldController packingGroupLookupController;
    @FXML private TextField properShippingNameField;
    @FXML private TextArea dangerousGoodsDescriptionArea;
    @FXML private TextField compatibilityGroupField;

    public PartDangerousGoodsController(PartViewModel viewModel) {
        this.viewModel = viewModel;
    }

    @FXML
    private void initialize() {
        hazardousCheckBox.selectedProperty().bindBidirectional(viewModel.hazardousProperty());
        LookupFieldBinding.bind(unNumberLookupController, "dangerousgoodsunnumber", "UN Number *", "Search UN number", viewModel.unNumberIdProperty(), viewModel.unNumberLookupQueryProperty());
        LookupFieldBinding.bind(hazardClassLookupController, "dangerousgoodshazardclass", "Hazard Class *", "Search hazard class", viewModel.hazardClassIdProperty(), viewModel.hazardClassLookupQueryProperty());
        LookupFieldBinding.bind(packingGroupLookupController, "dangerousgoodspackinggroup", "Packing Group *", "Search packing group", viewModel.packingGroupIdProperty(), viewModel.packingGroupLookupQueryProperty());
        properShippingNameField.textProperty().bindBidirectional(viewModel.properShippingNameProperty());
        dangerousGoodsDescriptionArea.textProperty().bindBidirectional(viewModel.dangerousGoodsDescriptionProperty());
        compatibilityGroupField.textProperty().bindBidirectional(viewModel.compatibilityGroupProperty());
    }
}

Dangerous Goods (Under Development) / Remarks (Under Development)

GUI review pending

Remarks (Under Development) details.

Source layout only: Disabled in the running application: this workspace is labelled Under Development.

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: Dangerous Goods (Under Development) / Remarks (Under Development)
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: partdangerousgoods--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;
    }
}

Dangerous Goods (Under Development) / Audit

GUI review pending

Audit details.

Source layout only: Disabled in the running application: this workspace is labelled Under Development.

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: Dangerous Goods (Under Development) / 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: partdangerousgoods--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;
    }
}

Links

BOM

BOM / Bill of Material

GUI review pending

Bill of Material details.

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: BOM / Bill of Material
Static JavaFX layout · 1280 × 674 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (22)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
View
viewModeComboBox
ComboBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Show all parent-items
showAllParentItemsCheckBox
CheckBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Show all sub-items
showAllSubItemsCheckBox
CheckBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Component Part
componentPartLookup
UUID search lookup
Controller-defined lookup
Saves the selected record ID; review display label, search fields and relationship cardinality.
External Part Number
componentPartNumberField
TextField
Generic/controller binding; inspect source
promptText=Use only when the part is not in Coflyr
Description
componentDescriptionField
TextField
Generic/controller binding; inspect source
promptText=External component description
Tree-Position
treePositionField
TextField
Generic/controller binding; inspect source
promptText=IPC tree or item position
Quantity *
quantityField
TextField
Generic/controller binding; inspect source
promptText=1
Reference Document
referenceDocumentField
TextField
Generic/controller binding; inspect source
promptText=IPC / CMM / drawing reference
Mandatory component
mandatoryCheckBox
CheckBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
New Item
New Item
Button
Static control
onAction=#newItem
Save Item
saveButton
Button
Generic/controller binding; inspect source
onAction=#save
Delete Item
deleteButton
Button
Generic/controller binding; inspect source
onAction=#delete
Refresh
Refresh
Button
Static control
onAction=#refresh
Bill of Material
bomTable
TableView
Generic/controller binding; inspect source
Review requiredness, validation and display.
Part Number
partNumberColumn
TableColumn
Generic/controller binding; inspect source
Review requiredness, validation and display.
Description
descriptionColumn
TableColumn
Generic/controller binding; inspect source
Review requiredness, validation and display.
Mat. Class
materialClassColumn
TableColumn
Generic/controller binding; inspect source
Review requiredness, validation and display.
Tree-Position
treePositionColumn
TableColumn
Generic/controller binding; inspect source
Review requiredness, validation and display.
Quantity
quantityColumn
TableColumn
Generic/controller binding; inspect source
Review requiredness, validation and display.
Measure-Unit
measureUnitColumn
TableColumn
Generic/controller binding; inspect source
Review requiredness, validation and display.
Ext. Part State
extendedPartStateColumn
TableColumn
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source evidence

Tab key: partbom--bom
Section: Entire form
FXML SHA-256: d170e74914ed36d7034d0e9e5504bad6c652085caf05a4c5553fdbe30afc8467

coflyr-fe/src/main/resources/com/coflyr/fe/part/views/part-bom-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.part.controllers.PartBomController"
      fillWidth="true" maxHeight="Infinity" maxWidth="Infinity" spacing="8"
      styleClass="standard-section-flat, part-link-section">
    <Label text="Bill of Material" styleClass="section-title"/>
    <Label fx:id="assemblyLabel" styleClass="field-hint"/>

    <GridPane maxWidth="Infinity" styleClass="form-fields-grid">
        <columnConstraints>
            <ColumnConstraints fillWidth="true" percentWidth="33.333" hgrow="ALWAYS"/>
            <ColumnConstraints fillWidth="true" percentWidth="33.333" hgrow="ALWAYS"/>
            <ColumnConstraints fillWidth="true" percentWidth="33.334" hgrow="ALWAYS"/>
        </columnConstraints>
        <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0">
            <Label text="View" styleClass="field-label"/>
            <ComboBox fx:id="viewModeComboBox" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
        </VBox>
        <CheckBox fx:id="showAllParentItemsCheckBox" text="Show all parent-items" styleClass="form-check-box" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
        <CheckBox fx:id="showAllSubItemsCheckBox" text="Show all sub-items" styleClass="form-check-box" GridPane.columnIndex="2" GridPane.rowIndex="0"/>

        <fx:include fx:id="componentPartLookup" source="../../views/controls/db-search-lookup-field.fxml" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
        <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="1">
            <Label text="External Part Number" styleClass="field-label"/>
            <TextField fx:id="componentPartNumberField" promptText="Use only when the part is not in Coflyr" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
        </VBox>
        <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="1">
            <Label text="Description" styleClass="field-label"/>
            <TextField fx:id="componentDescriptionField" promptText="External component description" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
        </VBox>

        <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="2">
            <Label text="Tree-Position" styleClass="field-label"/>
            <TextField fx:id="treePositionField" promptText="IPC tree or item position" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
        </VBox>
        <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="2">
            <Label text="Quantity *" styleClass="field-label"/>
            <TextField fx:id="quantityField" promptText="1" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
        </VBox>
        <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="2">
            <Label text="Reference Document" styleClass="field-label"/>
            <TextField fx:id="referenceDocumentField" promptText="IPC / CMM / drawing reference" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input"/>
        </VBox>
        <CheckBox fx:id="mandatoryCheckBox" text="Mandatory component" styleClass="form-check-box" GridPane.columnIndex="0" GridPane.columnSpan="3" GridPane.rowIndex="3"/>
    </GridPane>

    <HBox spacing="8">
        <Button text="New Item" onAction="#newItem" styleClass="secondary-button"/>
        <Button fx:id="saveButton" text="Save Item" onAction="#save" styleClass="primary-button"/>
        <Button fx:id="deleteButton" text="Delete Item" onAction="#delete" styleClass="danger-button"/>
        <Button text="Refresh" onAction="#refresh" styleClass="secondary-button"/>
    </HBox>
    <Label fx:id="messageLabel" wrapText="true" styleClass="message-label"/>

    <TableView fx:id="bomTable" VBox.vgrow="ALWAYS" prefHeight="260">
        <columns>
            <TableColumn fx:id="partNumberColumn" text="Part Number" prefWidth="170"/>
            <TableColumn fx:id="descriptionColumn" text="Description" prefWidth="230"/>
            <TableColumn fx:id="materialClassColumn" text="Mat. Class" prefWidth="110"/>
            <TableColumn fx:id="treePositionColumn" text="Tree-Position" prefWidth="130"/>
            <TableColumn fx:id="quantityColumn" text="Quantity" prefWidth="85"/>
            <TableColumn fx:id="measureUnitColumn" text="Measure-Unit" prefWidth="105"/>
            <TableColumn fx:id="extendedPartStateColumn" text="Ext. Part State" prefWidth="125"/>
        </columns>
    </TableView>
</VBox>
Controller: com.coflyr.fe.part.controllers.PartBomController
package com.coflyr.fe.part.controllers;

import com.coflyr.fe.part.services.PartAssignmentService;
import com.coflyr.fe.part.viewmodels.PartViewModel;
import com.coflyr.fe.security.FormPermissionService;
import com.coflyr.fe.shared.lookup.LookupFieldBinding;
import com.coflyr.fe.views.DbSearchLookupFieldController;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.Executor;

/** Editor and AMOS tree/list views for the selected part's bill of material. */
@Component
@Scope("prototype")
public class PartBomController {
    private static final String CONTROLLER = "PartBOM";
    private static final String TREE_VIEW = "Tree View";
    private static final String LIST_VIEW = "List View (sum)";

    private final PartViewModel part;
    private final PartAssignmentService service;
    private final Executor executor;
    private final FormPermissionService permissions;
    private final StringProperty componentPartId = new SimpleStringProperty("");
    private final StringProperty componentPartQuery = new SimpleStringProperty("");
    private final List<Map<?, ?>> loadedRows = new ArrayList<>();
    private String selectedBomId = "";
    private long generation;
    private boolean saving;

    @FXML private Label assemblyLabel;
    @FXML private ComboBox<String> viewModeComboBox;
    @FXML private CheckBox showAllParentItemsCheckBox;
    @FXML private CheckBox showAllSubItemsCheckBox;
    @FXML private DbSearchLookupFieldController componentPartLookupController;
    @FXML private TextField componentPartNumberField;
    @FXML private TextField componentDescriptionField;
    @FXML private TextField treePositionField;
    @FXML private TextField quantityField;
    @FXML private CheckBox mandatoryCheckBox;
    @FXML private TextField referenceDocumentField;
    @FXML private Button saveButton;
    @FXML private Button deleteButton;
    @FXML private Label messageLabel;
    @FXML private TableView<Map<?, ?>> bomTable;
    @FXML private TableColumn<Map<?, ?>, String> partNumberColumn;
    @FXML private TableColumn<Map<?, ?>, String> descriptionColumn;
    @FXML private TableColumn<Map<?, ?>, String> materialClassColumn;
    @FXML private TableColumn<Map<?, ?>, String> treePositionColumn;
    @FXML private TableColumn<Map<?, ?>, String> quantityColumn;
    @FXML private TableColumn<Map<?, ?>, String> measureUnitColumn;
    @FXML private TableColumn<Map<?, ?>, String> extendedPartStateColumn;

    public PartBomController(
            PartViewModel part,
            PartAssignmentService service,
            @Qualifier("formCommandExecutor") Executor executor,
            FormPermissionService permissions
    ) {
        this.part = part;
        this.service = service;
        this.executor = executor;
        this.permissions = permissions;
    }

    @FXML
    private void initialize() {
        LookupFieldBinding.bind(componentPartLookupController, "manageparts", "Component Part",
                "Search part number or description", componentPartId, componentPartQuery);
        viewModeComboBox.getItems().setAll(TREE_VIEW, LIST_VIEW);
        viewModeComboBox.setValue(TREE_VIEW);
        mandatoryCheckBox.setSelected(true);
        quantityField.setText("1");

        partNumberColumn.setCellValueFactory(cell -> new ReadOnlyStringWrapper(treePartNumber(cell.getValue())));
        descriptionColumn.setCellValueFactory(cell -> value(cell.getValue(), "PartDescription"));
        materialClassColumn.setCellValueFactory(cell -> value(cell.getValue(), "MaterialClass"));
        treePositionColumn.setCellValueFactory(cell -> value(cell.getValue(), "TreePosition"));
        quantityColumn.setCellValueFactory(cell -> value(cell.getValue(), "Quantity"));
        measureUnitColumn.setCellValueFactory(cell -> value(cell.getValue(), "MeasureUnit"));
        extendedPartStateColumn.setCellValueFactory(cell -> value(cell.getValue(), "ExtendedPartState"));
        bomTable.getSelectionModel().selectedItemProperty().addListener((obs, oldRow, row) -> loadEditor(row));

        part.selectedPartIdProperty().addListener((obs, oldId, newId) -> {
            clearEditor();
            refresh();
        });
        part.partNumberProperty().addListener((obs, oldValue, newValue) -> updateAssemblyLabel());
        showAllParentItemsCheckBox.selectedProperty().addListener((obs, oldValue, newValue) -> refresh());
        showAllSubItemsCheckBox.selectedProperty().addListener((obs, oldValue, newValue) -> refresh());
        viewModeComboBox.valueProperty().addListener((obs, oldValue, newValue) -> renderRows());
        updateAssemblyLabel();
        refresh();
    }

    @FXML
    private void newItem() {
        bomTable.getSelectionModel().clearSelection();
        clearEditor();
        messageLabel.setText("Enter one BOM component line.");
    }

    @FXML
    private void save() {
        String parentPartId = part.selectedPartIdProperty().get();
        if (saving || parentPartId.isBlank() || !permissions.canMutate("manageparts")) return;
        String linkedPartId = componentPartId.get().trim();
        String externalPartNumber = componentPartNumberField.getText().trim();
        if (linkedPartId.isBlank() && externalPartNumber.isBlank()) {
            messageLabel.setText("Select a component part or enter an external component part number.");
            return;
        }
        if (parentPartId.equals(linkedPartId)) {
            messageLabel.setText("A part cannot contain itself.");
            return;
        }

        int quantity;
        try {
            quantity = Integer.parseInt(quantityField.getText().trim());
        } catch (NumberFormatException exception) {
            messageLabel.setText("Quantity must be a whole number from 1 to 9999.");
            return;
        }
        if (quantity < 1 || quantity > 9999) {
            messageLabel.setText("Quantity must be from 1 to 9999.");
            return;
        }

        Map<String, Object> values = new LinkedHashMap<>();
        values.put("ParentPartId", parentPartId);
        values.put("ComponentPartId", linkedPartId.isBlank() ? null : linkedPartId);
        values.put("ComponentPartNumber", externalPartNumber);
        values.put("ComponentDescription", componentDescriptionField.getText().trim());
        values.put("Quantity", quantity);
        values.put("PositionReference", treePositionField.getText().trim());
        values.put("IsMandatory", mandatoryCheckBox.isSelected());
        values.put("ReferenceDocument", referenceDocumentField.getText().trim());

        String editId = selectedBomId;
        saving = true;
        updateButtons();
        messageLabel.setText(editId.isBlank() ? "Adding BOM component..." : "Updating BOM component...");
        executor.execute(() -> {
            try {
                if (editId.isBlank()) service.add(CONTROLLER, parentPartId, values);
                else service.update(CONTROLLER, editId, values);
                Platform.runLater(() -> {
                    saving = false;
                    clearEditor();
                    refresh();
                });
            } catch (Exception error) {
                if (error instanceof InterruptedException) Thread.currentThread().interrupt();
                Platform.runLater(() -> {
                    saving = false;
                    updateButtons();
                    messageLabel.setText("BOM component was not saved: " + error.getMessage());
                });
            }
        });
    }

    @FXML
    private void delete() {
        String id = selectedBomId;
        if (saving || id.isBlank() || !permissions.canMutate("manageparts")) return;
        saving = true;
        updateButtons();
        messageLabel.setText("Deleting BOM component...");
        executor.execute(() -> {
            try {
                service.delete(CONTROLLER, id);
                Platform.runLater(() -> {
                    saving = false;
                    clearEditor();
                    refresh();
                });
            } catch (Exception error) {
                if (error instanceof InterruptedException) Thread.currentThread().interrupt();
                Platform.runLater(() -> {
                    saving = false;
                    updateButtons();
                    messageLabel.setText("BOM component was not deleted: " + error.getMessage());
                });
            }
        });
    }

    @FXML
    private void refresh() {
        long request = ++generation;
        String partId = part.selectedPartIdProperty().get();
        updateAssemblyLabel();
        loadedRows.clear();
        bomTable.getItems().clear();
        updateButtons();
        if (partId.isBlank()) {
            messageLabel.setText("Save or select a part before editing its bill of material.");
            return;
        }
        messageLabel.setText("Loading bill of material...");
        boolean includeParents = showAllParentItemsCheckBox.isSelected();
        boolean includeChildren = showAllSubItemsCheckBox.isSelected();
        executor.execute(() -> {
            try {
                var rows = service.loadBom(partId, includeParents, includeChildren);
                Platform.runLater(() -> {
                    if (request != generation) return;
                    loadedRows.addAll(rows);
                    renderRows();
                    messageLabel.setText(rows.isEmpty() ? "No BOM components." : rows.size() + " BOM line(s).");
                });
            } catch (Exception error) {
                if (error instanceof InterruptedException) Thread.currentThread().interrupt();
                Platform.runLater(() -> {
                    if (request == generation)
                        messageLabel.setText("Could not load bill of material: " + error.getMessage());
                });
            }
        });
    }

    private void renderRows() {
        if (LIST_VIEW.equals(viewModeComboBox.getValue())) {
            Map<String, Map<String, Object>> summed = new LinkedHashMap<>();
            for (Map<?, ?> row : loadedRows) {
                String key = PartAssignmentService.text(row, "ComponentPartId") + "|"
                        + PartAssignmentService.text(row, "PartNumber");
                Map<String, Object> aggregate = summed.get(key);
                if (aggregate == null) {
                    aggregate = copy(row);
                    summed.put(key, aggregate);
                } else {
                    aggregate.put("Quantity", integer(aggregate.get("Quantity"))
                            + integer(PartAssignmentService.value(row, "Quantity")));
                }
                aggregate.put("Aggregated", true);
            }
            bomTable.getItems().setAll(summed.values());
        } else {
            bomTable.getItems().setAll(loadedRows);
        }
        clearEditor();
    }

    private void loadEditor(Map<?, ?> row) {
        if (row == null || Boolean.parseBoolean(PartAssignmentService.text(row, "Aggregated"))) {
            clearEditor();
            return;
        }
        selectedBomId = PartAssignmentService.text(row, "Id");
        componentPartId.set(PartAssignmentService.text(row, "ComponentPartId"));
        componentPartQuery.set(PartAssignmentService.text(row, "PartNumber"));
        componentPartNumberField.setText(PartAssignmentService.text(row, "ComponentPartNumber"));
        componentDescriptionField.setText(PartAssignmentService.text(row, "ComponentDescription"));
        treePositionField.setText(PartAssignmentService.text(row, "TreePosition"));
        quantityField.setText(PartAssignmentService.text(row, "Quantity"));
        mandatoryCheckBox.setSelected(Boolean.parseBoolean(PartAssignmentService.text(row, "IsMandatory")));
        referenceDocumentField.setText(PartAssignmentService.text(row, "ReferenceDocument"));
        updateButtons();
    }

    private void clearEditor() {
        selectedBomId = "";
        componentPartId.set("");
        componentPartQuery.set("");
        componentPartNumberField.clear();
        componentDescriptionField.clear();
        treePositionField.clear();
        quantityField.setText("1");
        mandatoryCheckBox.setSelected(true);
        referenceDocumentField.clear();
        updateButtons();
    }

    private void updateButtons() {
        boolean unavailable = saving || part.selectedPartIdProperty().get().isBlank()
                || !permissions.canMutate("manageparts") || LIST_VIEW.equals(viewModeComboBox.getValue());
        saveButton.setDisable(unavailable);
        deleteButton.setDisable(unavailable || selectedBomId.isBlank());
    }

    private void updateAssemblyLabel() {
        String number = part.partNumberProperty().get();
        assemblyLabel.setText(number == null || number.isBlank() ? "No parent assembly selected" : "Parent assembly: " + number);
    }

    private String treePartNumber(Map<?, ?> row) {
        String prefix = "Parent".equals(PartAssignmentService.text(row, "Direction")) ? "↑ " : "";
        int depth = Math.max(1, integer(PartAssignmentService.value(row, "Depth")));
        return prefix + "  ".repeat(Math.max(0, depth - 1)) + PartAssignmentService.text(row, "PartNumber");
    }

    private static StringProperty value(Map<?, ?> row, String key) {
        Object raw = PartAssignmentService.value(row, key);
        return new SimpleStringProperty(raw == null ? "" : String.valueOf(raw));
    }

    private static int integer(Object value) {
        if (value instanceof Number number) return number.intValue();
        try { return Integer.parseInt(String.valueOf(value)); }
        catch (RuntimeException ignored) { return 0; }
    }

    private static Map<String, Object> copy(Map<?, ?> source) {
        Map<String, Object> copy = new LinkedHashMap<>();
        source.forEach((key, value) -> copy.put(String.valueOf(key), value));
        return copy;
    }
}

BOM / Remarks

GUI review pending

Remarks details.

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: BOM / 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: partbom--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;
    }
}

BOM / Audit

GUI review pending

Audit details.

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: BOM / 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: partbom--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;
    }
}

Part Relationships

Effectivity

Effectivity / Main

GUI review pending

Main details.

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

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

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

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

On phones, controls are displayed as individual cards.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Effectivity / Configuration

GUI review pending

Configuration details.

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

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

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

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

On phones, controls are displayed as individual cards.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Effectivity / Part

GUI review pending

Part details.

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

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

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

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

On phones, controls are displayed as individual cards.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Effectivity / Rotable

GUI review pending

Rotable details.

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

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

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

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

On phones, controls are displayed as individual cards.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Effectivity / Validity

GUI review pending

Validity details.

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

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

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

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

On phones, controls are displayed as individual cards.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Effectivity / Remarks

GUI review pending

Remarks details.

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

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

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

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

On phones, controls are displayed as individual cards.

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

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

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

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

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

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

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

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

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

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

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

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

Effectivity / Audit

GUI review pending

Audit details.

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

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

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

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

On phones, controls are displayed as individual cards.

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

Tab key: parteffectivity--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;
    }
}

Part Requirements

Part Requirements / Main: Planning

GUI review pending

Part Requirements: Main: Planning.

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: Part Requirements / Main: Planning
Direct application capture · 2026-10-10T12:35:35.212478 · 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: Part Requirements / Main: Planning
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (1)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Work Template
WorkTemplateId
TextField
Generic/controller binding; inspect source
promptText=Record ID
Source evidence

Tab key: partrequirements--section-main-planning
Section: Entire form
FXML SHA-256: bf30d7989862dc01901f25e308a13b3055fa75db079226596839fcd7f262e10e

coflyr-fe/src/main/resources/com/coflyr/fe/amos/bespoke/sections/time-requirement--main-planning.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!-- Generated by scripts/split_amos_form_sections.py; edit the source form to change controls. -->
<ScrollPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" hbarPolicy="NEVER" maxHeight="Infinity" maxWidth="Infinity" styleClass="form-scroll-pane">
    <content>
        <VBox fillWidth="true" maxWidth="Infinity" spacing="8" styleClass="standard-section-flat">
            <Label text="Main: Planning" styleClass="section-title" />
            <GridPane maxWidth="Infinity" hgap="12" vgap="10" styleClass="form-fields-grid">
                <columnConstraints>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                </columnConstraints>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <Label text="Work Template" styleClass="field-label" />
                    <TextField fx:id="WorkTemplateId" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" promptText="Record ID" />
                </VBox>
            </GridPane>
        </VBox>
    </content>
</ScrollPane>

Part Requirements / Main: Identity

GUI review pending

Part Requirements: Main: Identity.

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: Part Requirements / Main: Identity
Direct application capture · 2026-10-10T12:35:38.666862 · 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: Part Requirements / Main: Identity
Static JavaFX layout · 1280 × 280 · 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
Reporting Back Message
ReportingBackMessage
TextField
Generic/controller binding; inspect source
promptText=Enter reporting-back message
Time Is Repetitive
TimeIsRepetitive
CheckBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Time Logic
TimeLogic
ComboBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Time Group Logic
TimeGroupLogic
ComboBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source evidence

Tab key: partrequirements--section-main-identity
Section: Entire form
FXML SHA-256: deaf03341dadaebaf164b35c06a9c40d251ffe5d56aea6283bee9dd4d9d0c783

coflyr-fe/src/main/resources/com/coflyr/fe/amos/bespoke/sections/time-requirement--main-identity.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!-- Generated by scripts/split_amos_form_sections.py; edit the source form to change controls. -->
<ScrollPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" hbarPolicy="NEVER" maxHeight="Infinity" maxWidth="Infinity" styleClass="form-scroll-pane">
    <content>
        <VBox fillWidth="true" maxWidth="Infinity" spacing="8" styleClass="standard-section-flat">
            <Label text="Main: Identity" styleClass="section-title" />
            <GridPane maxWidth="Infinity" hgap="12" vgap="10" styleClass="form-fields-grid">
                <columnConstraints>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                </columnConstraints>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <Label text="Reporting Back Message" styleClass="field-label" />
                    <TextField fx:id="ReportingBackMessage" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" promptText="Enter reporting-back message" />
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <CheckBox fx:id="TimeIsRepetitive" styleClass="form-check-box" text="Time Is Repetitive" />
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <Label text="Time Logic" styleClass="field-label" />
                    <ComboBox fx:id="TimeLogic" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" />
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.hgrow="ALWAYS">
                    <Label text="Time Group Logic" styleClass="field-label" />
                    <ComboBox fx:id="TimeGroupLogic" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" />
                </VBox>
            </GridPane>
        </VBox>
    </content>
</ScrollPane>

Part Requirements / Main: Classification

GUI review pending

Part Requirements: Main: Classification.

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: Part Requirements / Main: Classification
Direct application capture · 2026-10-10T12:35:42.053661 · 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: Part Requirements / Main: Classification
Static JavaFX layout · 1280 × 284 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (8)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Requirement Code
RequirementCode
TextField
Generic/controller binding; inspect source
Review requiredness, validation and display.
Part Requirement Type
RequirementTypeDefinitionId
DbSearchLookupField
Generic/controller binding; inspect source
Review requiredness, validation and display.
Compliance Type
ComplianceTypeDefinitionId
DbSearchLookupField
Generic/controller binding; inspect source
Review requiredness, validation and display.
Requirement Title
RequirementTitle
TextField
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source Type
SourceType
ComboBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Time Baseline Type
TimeBaselineType
ComboBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Requirement Group Order
RequirementGroupOrder
TextField
Generic/controller binding; inspect source
Review requiredness, validation and display.
Requirement Group Type
RequirementGroupType
ComboBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source evidence

Tab key: partrequirements--section-main-classification
Section: Entire form
FXML SHA-256: 261f05beaef082b443601a307350ecdb01aa04b0b28f8d5e49514b62671fa9e3

coflyr-fe/src/main/resources/com/coflyr/fe/part/views/part-requirement-classification.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import com.coflyr.fe.views.DbSearchLookupField?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<ScrollPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" hbarPolicy="NEVER" styleClass="form-scroll-pane">
    <content>
        <VBox fillWidth="true" spacing="8" styleClass="standard-section-flat">
            <Label text="Part Requirement Classification" styleClass="section-title"/>
            <GridPane hgap="12" vgap="10" styleClass="form-fields-grid">
                <columnConstraints>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS"/>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS"/>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS"/>
                </columnConstraints>
                <VBox styleClass="field-group"><Label text="Requirement Code" styleClass="field-label"/><TextField fx:id="RequirementCode" maxWidth="Infinity" styleClass="text-input"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1"><DbSearchLookupField fx:id="RequirementTypeDefinitionId" definition="amos-requirement-type-definition" label="Part Requirement Type" maxWidth="Infinity"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2"><DbSearchLookupField fx:id="ComplianceTypeDefinitionId" definition="amos-compliance-type-definition" label="Compliance Type" maxWidth="Infinity"/></VBox>
                <VBox styleClass="field-group" GridPane.rowIndex="1"><Label text="Requirement Title" styleClass="field-label"/><TextField fx:id="RequirementTitle" maxWidth="Infinity" styleClass="text-input"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="1"><Label text="Source Type" styleClass="field-label"/><ComboBox fx:id="SourceType" maxWidth="Infinity" styleClass="text-input"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="1"><Label text="Time Baseline Type" styleClass="field-label"/><ComboBox fx:id="TimeBaselineType" maxWidth="Infinity" styleClass="text-input"/></VBox>
                <VBox styleClass="field-group" GridPane.rowIndex="2"><Label text="Requirement Group Order" styleClass="field-label"/><TextField fx:id="RequirementGroupOrder" maxWidth="Infinity" styleClass="text-input"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="2"><Label text="Requirement Group Type" styleClass="field-label"/><ComboBox fx:id="RequirementGroupType" maxWidth="Infinity" styleClass="text-input"/></VBox>
            </GridPane>
        </VBox>
    </content>
</ScrollPane>

Part Requirements / Main: Measurements

GUI review pending

Part Requirements: Main: Measurements.

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: Part Requirements / Main: Measurements
Direct application capture · 2026-10-10T12:35:45.470622 · 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: Part Requirements / Main: Measurements
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (1)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Time Is Unlimited
TimeIsUnlimited
CheckBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source evidence

Tab key: partrequirements--section-main-measurements
Section: Entire form
FXML SHA-256: 2a27d351d1108f9db3858b6c509f4deff0212d103c55c140938e8d88d30ff861

coflyr-fe/src/main/resources/com/coflyr/fe/amos/bespoke/sections/time-requirement--main-measurements.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!-- Generated by scripts/split_amos_form_sections.py; edit the source form to change controls. -->
<ScrollPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" hbarPolicy="NEVER" maxHeight="Infinity" maxWidth="Infinity" styleClass="form-scroll-pane">
    <content>
        <VBox fillWidth="true" maxWidth="Infinity" spacing="8" styleClass="standard-section-flat">
            <Label text="Main: Measurements" styleClass="section-title" />
            <GridPane maxWidth="Infinity" hgap="12" vgap="10" styleClass="form-fields-grid">
                <columnConstraints>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                </columnConstraints>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <CheckBox fx:id="TimeIsUnlimited" styleClass="form-check-box" text="Time Is Unlimited" />
                </VBox>
            </GridPane>
        </VBox>
    </content>
</ScrollPane>

Part Requirements / Main: Dates

GUI review pending

Part Requirements: Main: 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: Part Requirements / Main: Dates
Direct application capture · 2026-10-10T12:35:48.881453 · 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: Part Requirements / Main: Dates
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (1)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Time Next Due Calculation Strategy
TimeNextDueCalculationStrategy
ComboBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source evidence

Tab key: partrequirements--section-main-dates
Section: Entire form
FXML SHA-256: 2c49baf11efe1c254f413905c58e3231d5116a0637912170a54227ba2d6383f0

coflyr-fe/src/main/resources/com/coflyr/fe/amos/bespoke/sections/time-requirement--main-dates.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!-- Generated by scripts/split_amos_form_sections.py; edit the source form to change controls. -->
<ScrollPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" hbarPolicy="NEVER" maxHeight="Infinity" maxWidth="Infinity" styleClass="form-scroll-pane">
    <content>
        <VBox fillWidth="true" maxWidth="Infinity" spacing="8" styleClass="standard-section-flat">
            <Label text="Main: Dates" styleClass="section-title" />
            <GridPane maxWidth="Infinity" hgap="12" vgap="10" styleClass="form-fields-grid">
                <columnConstraints>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                </columnConstraints>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <Label text="Time Next Due Calculation Strategy" styleClass="field-label" />
                    <ComboBox fx:id="TimeNextDueCalculationStrategy" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" />
                </VBox>
            </GridPane>
        </VBox>
    </content>
</ScrollPane>

Part Requirements / Effectivity

GUI review pending

Part Requirements: Effectivity.

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: Part Requirements / Effectivity
Direct application capture · 2026-10-10T12:35:52.974390 · 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: Part Requirements / Effectivity
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (1)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Effectivity
EffectivityId
TextField
Generic/controller binding; inspect source
promptText=Record ID
Source evidence

Tab key: partrequirements--section-effectivity
Section: Entire form
FXML SHA-256: cd1b476fa7e869f3448577663c35140f1df66337148e577955596f7d15db690d

coflyr-fe/src/main/resources/com/coflyr/fe/amos/bespoke/sections/time-requirement--effectivity.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!-- Generated by scripts/split_amos_form_sections.py; edit the source form to change controls. -->
<ScrollPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" hbarPolicy="NEVER" maxHeight="Infinity" maxWidth="Infinity" styleClass="form-scroll-pane">
    <content>
        <VBox fillWidth="true" maxWidth="Infinity" spacing="8" styleClass="standard-section-flat">
            <Label text="Effectivity" styleClass="section-title" />
            <GridPane maxWidth="Infinity" hgap="12" vgap="10" styleClass="form-fields-grid">
                <columnConstraints>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                </columnConstraints>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <Label text="Effectivity" styleClass="field-label" />
                    <TextField fx:id="EffectivityId" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" promptText="Record ID" />
                </VBox>
            </GridPane>
        </VBox>
    </content>
</ScrollPane>

Part Requirements / Partners & References

GUI review pending

Part Requirements: Partners & References.

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: Part Requirements / Partners & References
Direct application capture · 2026-10-10T12:36:02.555566 · 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: Part Requirements / Partners & References
Static JavaFX layout · 1280 × 280 · 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
Operator Business Partner
OperatorBusinessPartnerId
TextField
Generic/controller binding; inspect source
Review requiredness, validation and display.
Regulatory Authority Business Partner
RegulatoryAuthorityBusinessPartnerId
TextField
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source Publication
SourcePublicationId
TextField
Generic/controller binding; inspect source
Review requiredness, validation and display.
Requirement Group Parent
RequirementGroupParentId
TextField
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source evidence

Tab key: partrequirements--section-partners-references
Section: Entire form
FXML SHA-256: 0f8ede0008c4efd43970516ca9073b27bbd36bb852314735771c50095502c8c6

coflyr-fe/src/main/resources/com/coflyr/fe/part/views/part-requirement-references.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<ScrollPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" hbarPolicy="NEVER" styleClass="form-scroll-pane">
    <content>
        <VBox fillWidth="true" spacing="8" styleClass="standard-section-flat">
            <Label text="Part Requirement References" styleClass="section-title"/>
            <GridPane hgap="12" vgap="10" styleClass="form-fields-grid">
                <columnConstraints>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS"/>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS"/>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS"/>
                </columnConstraints>
                <VBox styleClass="field-group"><Label text="Operator Business Partner" styleClass="field-label"/><TextField fx:id="OperatorBusinessPartnerId" maxWidth="Infinity" styleClass="text-input"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1"><Label text="Regulatory Authority Business Partner" styleClass="field-label"/><TextField fx:id="RegulatoryAuthorityBusinessPartnerId" maxWidth="Infinity" styleClass="text-input"/></VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2"><Label text="Source Publication" styleClass="field-label"/><TextField fx:id="SourcePublicationId" maxWidth="Infinity" styleClass="text-input"/></VBox>
                <VBox styleClass="field-group" GridPane.rowIndex="1"><Label text="Requirement Group Parent" styleClass="field-label"/><TextField fx:id="RequirementGroupParentId" maxWidth="Infinity" styleClass="text-input"/></VBox>
            </GridPane>
        </VBox>
    </content>
</ScrollPane>

Part Requirements / Quantities & Measurements

GUI review pending

Part Requirements: Quantities & Measurements.

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: Part Requirements / Quantities & Measurements
Direct application capture · 2026-10-10T12:36:06.596941 · 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: Part Requirements / Quantities & Measurements
Static JavaFX layout · 1280 × 292 · 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
Time Limits
TimeLimits
TextArea
Generic/controller binding; inspect source
promptText=[{"LimitType":"Interval","Dimension":"FH","Amount":3000,"GroupNumber":1,"Sequence":0,"BaselineType":"LastAccomplishment","NextDueCalculationStrategy":"FromLastAccomplishment"}]
Time Threshold Value
TimeThresholdValue
TextField
Generic/controller binding; inspect source
promptText=Enter time threshold value
Limit Row
TimeLimitCounterRow
ComboBox
Generic/controller binding; inspect source
promptText=Select a time limit
Counter Definition
TimeLimitCounterDefinition
DbSearchLookupField
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source evidence

Tab key: partrequirements--section-quantities-measurements
Section: Entire form
FXML SHA-256: e9d6e0c7e5e122dd92eeb69b7eaa7cd63f5f518dbe7fb294301740f233426f2c

coflyr-fe/src/main/resources/com/coflyr/fe/amos/bespoke/sections/time-requirement--quantities-measurements.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.coflyr.fe.views.DbSearchLookupField?>
<!-- Generated by scripts/split_amos_form_sections.py; edit the source form to change controls. -->
<ScrollPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" hbarPolicy="NEVER" maxHeight="Infinity" maxWidth="Infinity" styleClass="form-scroll-pane">
    <content>
        <VBox fillWidth="true" maxWidth="Infinity" spacing="8" styleClass="standard-section-flat">
            <Label text="Quantities &amp; Measurements" styleClass="section-title" />
            <GridPane maxWidth="Infinity" hgap="12" vgap="10" styleClass="form-fields-grid">
                <columnConstraints>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                </columnConstraints>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS" GridPane.columnSpan="3">
                    <Label text="Time Limits" styleClass="field-label" />
                    <TextArea fx:id="TimeLimits" styleClass="form-text-area" prefRowCount="6" promptText="[{&quot;LimitType&quot;:&quot;Interval&quot;,&quot;Dimension&quot;:&quot;FH&quot;,&quot;Amount&quot;:3000,&quot;GroupNumber&quot;:1,&quot;Sequence&quot;:0,&quot;BaselineType&quot;:&quot;LastAccomplishment&quot;,&quot;NextDueCalculationStrategy&quot;:&quot;FromLastAccomplishment&quot;}]" />
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.hgrow="ALWAYS">
                    <Label text="Time Threshold Value" styleClass="field-label" />
                    <TextField fx:id="TimeThresholdValue" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" promptText="Enter time threshold value" />
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="1" GridPane.hgrow="ALWAYS">
                    <Label text="Limit Row" styleClass="field-label" />
                    <ComboBox fx:id="TimeLimitCounterRow" maxWidth="Infinity" promptText="Select a time limit" />
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="1" GridPane.hgrow="ALWAYS">
                    <DbSearchLookupField fx:id="TimeLimitCounterDefinition" definition="amos-counter-definition" label="Counter Definition" maxWidth="Infinity" />
                    <Label text="Selection uses the Custom dimension for this limit." wrapText="true" styleClass="field-hint" />
                </VBox>
            </GridPane>
        </VBox>
    </content>
</ScrollPane>

Part Requirements / Rules & Options

GUI review pending

Part Requirements: Rules & Options.

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: Part Requirements / Rules & Options
Direct application capture · 2026-10-10T12:36:10.632335 · 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: Part Requirements / Rules & 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 (4)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Is Externally Controlled
IsExternallyControlled
CheckBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Is Performable On Shelf
IsPerformableOnShelf
CheckBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Is Pro Rata Calculation
IsProRataCalculation
CheckBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Is Requirement Group Header
IsRequirementGroupHeader
CheckBox
Generic/controller binding; inspect source
Review requiredness, validation and display.
Source evidence

Tab key: partrequirements--section-rules-options
Section: Entire form
FXML SHA-256: 0e1b73b9fe3f132b804287384b32407f02c8fd8c55301928446e103113e9b41f

coflyr-fe/src/main/resources/com/coflyr/fe/amos/bespoke/sections/time-requirement--rules-options.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!-- Generated by scripts/split_amos_form_sections.py; edit the source form to change controls. -->
<ScrollPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" hbarPolicy="NEVER" maxHeight="Infinity" maxWidth="Infinity" styleClass="form-scroll-pane">
    <content>
        <VBox fillWidth="true" maxWidth="Infinity" spacing="8" styleClass="standard-section-flat">
            <Label text="Rules &amp; Options" styleClass="section-title" />
            <GridPane maxWidth="Infinity" hgap="12" vgap="10" styleClass="form-fields-grid">
                <columnConstraints>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                </columnConstraints>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <CheckBox fx:id="IsExternallyControlled" styleClass="form-check-box" text="Is Externally Controlled" />
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="1" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <CheckBox fx:id="IsPerformableOnShelf" styleClass="form-check-box" text="Is Performable On Shelf" />
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="2" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <CheckBox fx:id="IsProRataCalculation" styleClass="form-check-box" text="Is Pro Rata Calculation" />
                </VBox>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.hgrow="ALWAYS">
                    <CheckBox fx:id="IsRequirementGroupHeader" styleClass="form-check-box" text="Is Requirement Group Header" />
                </VBox>
            </GridPane>
        </VBox>
    </content>
</ScrollPane>

Part Requirements / Operational Notes

GUI review pending

Part Requirements: Operational Notes.

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: Part Requirements / Operational Notes
Direct application capture · 2026-10-10T12:36:14.567115 · 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: Part Requirements / Operational Notes
Static JavaFX layout · 1280 × 280 · Click to inspect at full size. This image is a source layout, not a live application screenshot.
Control inventory (1)

On phones, controls are displayed as individual cards.

Label / control IDControl / bindingDeclared behaviour / review pointGUI decision
Requirement Text
RequirementText
TextField
Generic/controller binding; inspect source
promptText=Enter requirement text
Source evidence

Tab key: partrequirements--section-operational-notes
Section: Entire form
FXML SHA-256: c04b38f326f3ae7a6169ef9daae888cd27a3ac7e5ea9e0d8c3d1be4e2a6dd225

coflyr-fe/src/main/resources/com/coflyr/fe/amos/bespoke/sections/time-requirement--operational-notes.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!-- Generated by scripts/split_amos_form_sections.py; edit the source form to change controls. -->
<ScrollPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" hbarPolicy="NEVER" maxHeight="Infinity" maxWidth="Infinity" styleClass="form-scroll-pane">
    <content>
        <VBox fillWidth="true" maxWidth="Infinity" spacing="8" styleClass="standard-section-flat">
            <Label text="Operational Notes" styleClass="section-title" />
            <GridPane maxWidth="Infinity" hgap="12" vgap="10" styleClass="form-fields-grid">
                <columnConstraints>
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                    <ColumnConstraints percentWidth="33.333333" hgrow="ALWAYS" fillWidth="true" />
                </columnConstraints>
                <VBox styleClass="field-group" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS">
                    <Label text="Requirement Text" styleClass="field-label" />
                    <TextField fx:id="RequirementText" maxWidth="Infinity" prefWidth="1.0" styleClass="text-input" promptText="Enter requirement text" />
                </VBox>
            </GridPane>
        </VBox>
    </content>
</ScrollPane>

Part Requirements / Remarks

GUI review pending

Remarks 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: Part Requirements / Remarks
Direct application capture · 2026-10-10T12:36:33.652157 · 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: Part Requirements / 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: partrequirements--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;
    }
}

Part Requirements / Audit

GUI review pending

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: Part Requirements / Audit
Direct application capture · 2026-10-10T12:36:22.600303 · 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: Part Requirements / 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: partrequirements--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;
    }
}

Part Requirements / 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: Part Requirements / Revision
Direct application capture · 2026-10-10T12:36:18.492140 · 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: Part Requirements / 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: partrequirements--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;
    }
}

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
DrawingNumber
Records drawing number for this part master.
string · NullableEngineering & Logistics / Drawing Number
PartName
Records part name for this part master.
string · RequiredMain / Part Name *
PartNumber
Records part number for this part master.
string · RequiredMain / Part Number *
ProperShippingName
Records proper shipping name for this part master.
string · NullableDetails / Proper Shipping Name
ActivateReordering
Indicates whether activate reordering applies to this part master.
bool · RequiredReorder / Activate Reordering
CalculateHistoricalForecast
Indicates whether calculate historical forecast applies to this part master.
bool · RequiredReorder / Calculate Historical Forecast
CalculateReorderLevel
Indicates whether calculate reorder level applies to this part master.
bool · RequiredReorder / Calculate Reorder Level
CalculateSafetyStock
Indicates whether calculate safety stock applies to this part master.
bool · RequiredReorder / Calculate Safety Stock
IndividualizeReorderingValuesByEntity
Indicates whether individualize reordering values by entity applies to this part master.
bool · RequiredReorder / Individualize by Entity
IsAirworthinessCritical
Indicates whether airworthiness critical applies to this part master.
bool · RequiredNo direct binding match — review mapping or deliberate omission
IsFixedAsset
Indicates whether fixed asset applies to this part master.
bool? · NullableNo direct binding match — review mapping or deliberate omission
IsHazardous
Indicates whether hazardous applies to this part master.
bool? · NullableNo direct binding match — review mapping or deliberate omission
IsPma
Indicates whether pma applies to this part master.
bool? · NullableNo direct binding match — review mapping or deliberate omission
IsRepairable
Indicates whether repairable applies to this part master.
bool? · NullableNo direct binding match — review mapping or deliberate omission
EndUseValidUntil
Records end use valid until for this part master.
DateTime? · NullableCustoms / End Use Valid Until
StoreTime
Records the store time for this part master.
int? · NullableEngineering & Logistics / Store Time
StoreTimeDays
Records the store time days for this part master.
int? · NullableEngineering & Logistics / Store Time Days
AveragePriceLocal
Records average price local for this part master.
decimal? · NullableFinancial / Average Price Local
AveragePriceMarket
Records average price market for this part master.
decimal? · NullableFinancial / Average Price Market
AverageRepairCost
Stores the average repair cost used by this part master.
decimal? · NullableFinancial / Average Repair Cost
FinancialQuantity
Stores the financial quantity used by this part master.
decimal? · NullableFinancial / Financial Quantity
IdealQuantity
Stores the ideal quantity used by this part master.
int? · NullableReorder / Ideal Quantity
PurchasePrice
Stores the purchase price used by this part master.
decimal? · NullableFinancial / Purchase Price
SalesPrice
Stores the sales price used by this part master.
decimal? · NullableFinancial / Sales Price
Weight
Records weight for this part master.
decimal? · NullableEngineering & Logistics / Weight
CustomsDescription
Records customs description for this part master.
string · NullableCustoms / Customs Description
DangerousGoodsDescription
Records dangerous goods description for this part master.
string · NullableDetails / Dangerous Goods Description
DrawingRevision
Records drawing revision for this part master.
string · NullableEngineering & Logistics / Drawing Revision
SystemAuditId
Links to system audit history when this model stores the reference.
Guid? · NullableNo direct binding match — review mapping or deliberate omission
UpdateId
Links to the preceding revision or update record.
Guid? · NullableNo direct binding match — review mapping or deliberate omission
AlertLevel
Records alert level for this part master.
int? · NullableReorder / Alert Level
ApplicabilityScope
Records applicability scope for this part master.
int · RequiredMain / Applicability Scope
Classification
Records classification for this part master.
string · NullableCustoms / Classification
CombinationRotableCode
Records combination rotable code for this part master.
string · NullableNo direct binding match — review mapping or deliberate omission
CompatibilityGroup
Records compatibility group for this part master.
string · NullableDetails / Compatibility Group
FinancialEntity
Records financial entity for this part master.
string · NullableFinancial / Financial Entity
MaterialClass
Records material class for this part master.
string · NullableMain / Material Class
MaxShopVisits
Records max shop visits for this part master.
int? · NullableEngineering & Logistics / Max Shop Visits
MinimumSafetyStock
Records minimum safety stock for this part master.
int? · NullableReorder / Minimum Safety Stock
MinimumShelfLifeDays
Records minimum shelf life days for this part master.
int? · NullableEngineering & Logistics / Minimum Shelf Life Days
PartSpecialCode
Records part special code for this part master.
string · NullableMain / Special Code
ReorderLevel
Records reorder level for this part master.
int? · NullableReorder / Reorder Level
ShelfLifeDays
Records shelf life days for this part master.
int? · NullableEngineering & Logistics / Shelf Life Days
ShopVisitResetCondition
Records shop visit reset condition for this part master.
string · NullableEngineering & Logistics / Shop Visit Reset Condition
Specification
Records specification for this part master.
string · NullableEngineering & Logistics / Specification
Value
Records value for this part master.
decimal? · NullableNo direct binding match — review mapping or deliberate omission

Runtime review checklist

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