All files / webapp/controller MyProductsAdd.controller.ts

98.11% Statements 52/53
75% Branches 15/20
100% Functions 12/12
98.11% Lines 52/53

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176                        4x                                         4x                       4x   28x     4x   4x             4x         34x   34x 4x     30x 30x               6x   6x 6x                   3x     3x 9x 9x   9x 9x   9x 3x 3x       3x   3x         7x 119x   119x 14x 105x 105x 105x                   3x 1x 1x     2x 34x 34x                     2x 2x 2x   2x   2x 2x 2x   2x   2x   2x   2x         1x 1x      
import BaseController from "./BaseController";
 
import Input from "sap/m/Input";
import Select from "sap/m/Select";
import JSONModel from "sap/ui/model/json/JSONModel";
import MessageToast from "sap/m/MessageToast";
import Event from "sap/ui/base/Event";
 
import { CURRENT_STATUS_VALUES } from "../model/currentStatus";
import { PamProduct } from "../model/productKey";
 
/** Maps each form control's view id to the PAM field it edits, mirroring the product detail page's fields. */
const FORM_FIELDS: Array<{ id: string; path: string }> = [
    { id: "addProductVersion", path: "version" },
    { id: "addProductName", path: "name" },
    { id: "addProductProduct", path: "product" },
    { id: "addProductLine", path: "line" },
    { id: "addProductCategory", path: "category" },
    { id: "addProductAddOn", path: "addOn" },
    { id: "addProductStatus", path: "status" },
    { id: "addProductRaPuna", path: "ra_puna" },
    { id: "addProductRaPua", path: "ra_pua" },
    { id: "addProductUa", path: "ua" },
    { id: "addProductHanaStatus", path: "hanaStatus" },
    { id: "addProductHanaDate", path: "hanaDate" },
    { id: "addProductEomm", path: "eomm" },
    { id: "addProductEom", path: "eom" },
    { id: "addProductEop1", path: "eop1" },
    { id: "addProductEosk", path: "eosk" },
    { id: "addProductUrl", path: "url" }
];
 
/** Form control ids that must have a value before the product can be saved. */
const REQUIRED_FIELD_IDS: string[] = [
    "addProductVersion",
    "addProductName",
    "addProductEomm"
];
 
/**
 * @namespace de.itsfullofstars.sap.pam.controller
 */
export default class MyProductsAdd extends BaseController {
    /** Sets up the status dropdown's options and wires the route handler that resets the form. */
    public onInit(): void {
        const statusOptionsModel = new JSONModel([
            { key: "", text: this.getText("myProducts.notSpecified") },
            ...CURRENT_STATUS_VALUES.map((value) => ({ key: value, text: value }))
        ]);
 
        this.setModel(statusOptionsModel, "statusOptions");
 
        this.getRouter()
            .getRoute("myproductsadd")
            ?.attachPatternMatched(this.onRouteMatched.bind(this));
    }
 
    /** Resets the (possibly still-filled) form whenever this route is (re-)entered. */
    private onRouteMatched(): void {
        this.resetForm();
    }
 
    /** Reads one form control's current value, trimmed. */
    private getControlValue(id: string): string {
        const control = this.byId(id);
 
        if (control instanceof Select) {
            return control.getSelectedKey().trim();
        }
 
        Eif (control instanceof Input) {
            return control.getValue().trim();
        }
 
        return "";
    }
 
    /** Clears a required Input's error state as soon as it has a value again. */
    public onRequiredFieldChange(event: Event): void {
        const input = event.getSource<Input>();
 
        Eif (input.getValue().trim()) {
            input.setValueState("None");
        }
    }
 
    /**
     * Validates the required fields (see {@link REQUIRED_FIELD_IDS}),
     * marking empty ones with an inline error.
     * @returns `true` if all required fields have a value
     */
    private validateRequiredFields(): boolean {
        let allValid = true;
        let firstInvalidInput: Input | undefined;
 
        REQUIRED_FIELD_IDS.forEach((id) => {
            const input = this.byId(id) as Input;
            const isEmpty = !input.getValue().trim();
 
            input.setValueState(isEmpty ? "Error" : "None");
            input.setValueStateText(this.getText("msg.fieldRequired"));
 
            if (isEmpty) {
                allValid = false;
                firstInvalidInput ??= input;
            }
        });
 
        firstInvalidInput?.focus();
 
        return allValid;
    }
 
    /** Resets every form field back to blank/unselected and clears error states. */
    private resetForm(): void {
        FORM_FIELDS.forEach(({ id }) => {
            const control = this.byId(id);
 
            if (control instanceof Select) {
                control.setSelectedKey("");
            } else Eif (control instanceof Input) {
                control.setValue("");
                control.setValueState("None");
            }
        });
    }
 
    /**
     * Validates and, on success, appends the form's values as a new entry
     * to the "ownProducts" model, then returns to the MyProducts page.
     */
    public onSave(): void {
        if (!this.validateRequiredFields()) {
            MessageToast.show(this.getText("msg.myProductsMissingRequiredFields"));
            return;
        }
 
        const product = FORM_FIELDS.reduce<PamProduct>((entry, field) => {
            entry[field.path] = this.getControlValue(field.id);
            return entry;
        }, {});
 
        /*
         * MyProducts' and SupportStatus' tables only ever render rows from
         * the "pam" model (filtered down to the user's own-products
         * selection) - a manually created product needs a row there too,
         * or it would be saved into "ownProducts" yet never show up
         * anywhere. It is appended, never inserted, so it doesn't shift the
         * index-based productdetail/{index} route of any existing entry.
         */
        const pamModel = this.getOwnerComponent().getModel("pam") as JSONModel;
        const pamData = pamModel.getData() as PamProduct[] | undefined;
        const existingPamProducts = Array.isArray(pamData) ? pamData : [];
 
        pamModel.setData([...existingPamProducts, product]);
 
        const ownProductsModel = this.getOwnerComponent().getModel("ownProducts") as JSONModel;
        const ownProducts = ownProductsModel.getData() as PamProduct[] | undefined;
        const existingOwnProducts = Array.isArray(ownProducts) ? ownProducts : [];
 
        ownProductsModel.setData([...existingOwnProducts, { ...product }]);
 
        this.resetForm();
 
        MessageToast.show(this.getText("msg.productAddedToOwnProducts"));
 
        this.navTo("myproducts");
    }
 
    /** Discards the form and returns to the MyProducts page. */
    public onCancel(): void {
        this.resetForm();
        this.navTo("myproducts");
    }
}