Skip to content

Change Reasons

The purpose of this service is to intercept the moment of editing an entity and request the reason for the change, if needed.

Important

Make sure that the entity that you want to add change reason specification is implementing Audit trails and it is also described in Entity Management.

Implementation

There are a few steps to follow in order to implement Change Reasons:

Backend changes

Add the dependency in our Suite module. Here you have two scenarios. If you have the entity and the Dto in the same Application module, you can specify the Entity and UpdateDto together. Also, you can add conditions to specified properties of that Entity. In addition, you can add conditions to the properties of that Entity. Depending on the type of property, different types of operators will be offered. Later on, these conditions will be translated to a graphql filter condition.

I.e.:

C#
1
2
3
4
5
6
7
builder.DependsOn<ChangeReasonsModule, ChangeReasonsModuleOptions>(
    opts =>
    {
        opts.AddEntityType<Entity, DtoUpdateEntity>()
            .AddCondition(x => x.DisplayName).EqualsTo("dummy display name")
            .AddCondition(x => x.InStockQuantity).LowerThanEquals(10);
    });

Important

You can also just set the entity type and then set the full name of the entity type and its condition as string in the appsettings

The other case is when you have the Dto and the Entity in different Application Modules. You can also specify them separated: The entity with its GraphQL condition in one module and the dto input type in the other one. I.e.:

C#
// In the Application Module where the Entity is located:
builder.DependsOn<ChangeReasonsModule, ChangeReasonsModuleOptions>(
    opts =>
    {
        opts.AddEntityType<Entity>()
            .AddCondition(x => x.DisplayName).EqualsTo("dummy display name")
            .AddCondition(x => x.inStockQuantity).LowerThanEquals(10);
    });

// In the Application Module where the Dto is located:
builder.DependsOn<ChangeReasonsModule, ChangeReasonsModuleOptions>(
    opts =>
    {
        opts.AddInputType<DtoUpdateEntity>();
    });

Important

Note that we only add UpdateDto as the input type, since we only care about update changes and not creation.

The above configuration will create a custom query for that Entity and check whether the condition applies or not in order to make required the change reason in the mutation.

Later, that change reason will be retrieved from the mutation and will be stored in the audit entity change.

Frontend changes

Suite form page

The following implementation is just for suite form pages. Implementation for Custom form pages will be explained later.

TypeScript
1
2
3
4
5
6
7
this.customizeForm(
    (b) =>
        b
            .useEntityDescriptor(ENTITY_WELL_KNOWN_NAME)
            .supportChangeReason()
    // Field customizations ...
);

The above configuration will hide change reason field from the auto-generated form page and set in the store the well known name corresponding to that entity.

With those simple changes, when the user clicks on 'Save' button, the backend checks whether the change reason is required or not, and depending on that, it will pop up a dialog requesting the reason of the change. That will be added to the form group value and sent along with mutation execution.

Later on, you will see the change reason values in the audit trails view of that entity.

Custom form page

In custom forms, the implementation varies a bit, here are the steps to follow:

TypeScript
import {
    CHANGE_REASON_DIALOG,
    ChangeReasonActions,
    ChangeReasonComponentStore,
    openChangeReasonDialog
} from '@itsynch/suite/change-reasons';

@Component({
    standalone: true,
    imports: [ CommonModule ],
    selector: 'its-update-entity-form',
    templateUrl: ยดยด,
    changeDetection: ChangeDetectionStrategy.OnPush,
    providers: [ ChangeReasonComponentStore ]
})
export class UpdateEntityComponent {

    constructor(private changeReasonComponentStore: ChangeReasonComponentStore)
    {}

    // This is the on 'Save' button execution
    public onSubmit() {
        const correlationId = this.form-group-name.controls['correlationId']
            .value as unknown as string;

        this.subscribeToChangeReasonChanges();

        // Note: If you check whether the form group is dirty or not, ONLY
        // dispatch this action if form is dirty.
        this.store.dispatch(
            ChangeReasonActions.checkForChangeReasonRequested({
                correlationId: correlationId,
                wellKnownName: ENTITY_WELL_KNOWN_NAME
            })
        );
    }

    private subscribeToChangeReasonChanges() {
        this.changeReasonComponentStore.isChangeReasonRequired$
            .pipe(
                takeUntil(this.changeReasonComponentStore.destroy$),
                filter(
                    (isRequired) =>
                        isRequired !== null && isRequired !== undefined
                ),
                tap((isRequired) => {
                    if (isRequired) {
                        this.changeReasonComponentStore.dispatch(
                            openChangeReasonDialog({
                                dialogId: CHANGE_REASON_DIALOG
                            })
                        );
                    } else {
                        this.executeSubmit();
                    }
                })
            )
            .subscribe();

        this.changeReasonComponentStore.changeReason$
            .pipe(
                takeUntil(this.changeReasonComponentStore.destroy$),
                filter((changeReason) => !!changeReason),
                tap((changeReason) => {
                    // Add the change reason value to the form group value and submit.
                    this.form-group-name.setControl(
                        'changeReason',
                        new UntypedFormControl(changeReason)
                    );

                    this.executeSubmit();
                })
            )
            .subscribe();
    }

    private executeSubmit() {
        // Move the Submit button logic here.
    }
}

In summary, the necessary changes are:

  1. Import, provide ChangeReasonComponentStore in your component and inject it in the constructor. This component store will be in charge of query if change reason is required or not and will store the change reason value, if any.

  2. Run the snippet named its-change-reason. That will add onSubmit(), subscribeToChangeReasonChanges() and executeSubmit() to your component. You will need to replace the form-group-name with you formGroup name. Also, you'll need to move the submission logic to the executeSubmit method.

With those changes, you will achieve the same behavior as in the form page component. The reason of the change will be populated in the audit entity change. If audit trails by entity is implemented in your component, you will be able to see the change reasons in the audit entity.