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);
}
}
}