Skip to content

Commit

Permalink
Fix #278 - Create RecordLogic Action
Browse files Browse the repository at this point in the history
  • Loading branch information
mpuyosa91 committed Nov 15, 2023
1 parent b321602 commit 00278c3
Show file tree
Hide file tree
Showing 12 changed files with 983 additions and 39 deletions.
1 change: 1 addition & 0 deletions core/app/common/src/lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from './metadata/widget.metadata';
export * from './record/favorites.model';
export * from './record/field.model';
export * from './record/recently-viewed.model';
export * from './record/record-logic-action.model';
export * from './record/record.model';
export * from './record/record-mappers/record-mapper.model';
export * from './record/record-mappers/record-mapper.registry';
Expand Down
41 changes: 41 additions & 0 deletions core/app/common/src/lib/record/record-logic-action.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
* Copyright (C) 2023 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Supercharged by SuiteCRM".
*/
import { Action } from '../actions/action.model'
import { AttributeDependency } from './field.model'


export interface RecordLogicMap {
[recordLogicName: string]: RecordLogic;
}

export interface RecordLogic extends Action {
params?: {
fieldDependencies?: string[];
attributeDependencies?: AttributeDependency[];
triggerOnEvents?: [LineActionEvent: boolean][];
[key: string]: any;
};
}
4 changes: 4 additions & 0 deletions core/app/core/src/lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ export * from './services/formatters/number/number-formatter.service';
export * from './services/formatters/phone/phone-formatter.service';
export * from './services/language/dynamic-label.service';
export * from './services/local-storage/local-storage.service';
export * from './services/logic/active-logic-checker.service';
export * from './services/message/message.service';
export * from './services/metadata/base-metadata.resolver';
export * from './services/metadata/base-module.resolver';
Expand Down Expand Up @@ -625,5 +626,8 @@ export * from './views/record/components/record-header/record-header.component';
export * from './views/record/components/record-header/record-header.module';
export * from './views/record/components/record-view/record.component';
export * from './views/record/components/record-view/record.module';
export * from './views/record/record-logic/record-logic.action';
export * from './views/record/record-logic/record-logic.manager';
export * from './views/record/record-logic/record-logic.model';
export * from './views/record/store/record-view/record-view.store.model';
export * from './views/record/store/record-view/record-view.store';
184 changes: 184 additions & 0 deletions core/app/core/src/lib/services/logic/active-logic-checker.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/**
* SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
* Copyright (C) 2023 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Supercharged by SuiteCRM".
*/
import { isArray, isEmpty } from 'lodash-es';
import { Injectable } from '@angular/core';
import {
Record,
Action,
Field,
StringArrayMap,
StringArrayMatrix,
ViewMode,
FieldAttribute,
ALL_VIEW_MODES,
LogicRuleValues,
} from 'common';
import { ConditionOperatorManager } from '../condition-operators/condition-operator.manager';

@Injectable({
providedIn: 'root',
})
export class ActiveLogicChecker {

modes: ViewMode[] = ALL_VIEW_MODES;

constructor(
protected operatorManager: ConditionOperatorManager
) {
}

public run(record: Record, action: Action): boolean {
if (!record || !action) {
return true;
}

const activeOnFields: StringArrayMap = action.params?.activeOnFields || {};

const activeOnAttributes: StringArrayMatrix = action.params?.activeOnAttributes || {};

return this.isActive(record, activeOnFields, activeOnAttributes);
}

/**
* Check if any of the configured values is currently set
*
* @param {Record} record Record
* @param {StringArrayMap} activeOnFields Active On Fields
* @param {StringArrayMatrix} activeOnAttributes Active On Attributes
* @returns {boolean} true if any of the configured values is currently set
*/
protected isActive(
record: Record,
activeOnFields: StringArrayMap,
activeOnAttributes: StringArrayMatrix
): boolean {
let isActive = true;
if (!isEmpty(activeOnFields)) {
isActive = isActive && this.areFieldsActive(record, activeOnFields);
}
if (!isEmpty(activeOnAttributes)) {
isActive = isActive && this.areAttributesActive(record, activeOnAttributes);
}

return isActive;
}

/**
* Are fields active
*
* @param {Record} record Record
* @param {StringArrayMap} activeOnFields StringArrayMap
* @returns {boolean} true are fields active
*/
protected areFieldsActive(record: Record, activeOnFields: StringArrayMap): boolean {
let areActive = true;

Object.entries(activeOnFields).forEach(([fieldKey, activeValues]) => {
if (!areActive) {
return;
}

const field = (record.fields ?? {})[fieldKey] ?? null;
if (!field || isEmpty(activeValues)) {
return;
}

areActive = this.isValueActive(record, field, activeValues);
});

return areActive;
}

/**
* Are attributes active
*
* @param {Record} record Record
* @param {StringArrayMatrix} activeOnAttributes Active On Attributes
* @returns {boolean} true if are attributes active
*/
protected areAttributesActive(record: Record, activeOnAttributes: StringArrayMatrix): boolean {
let areActive = true;

Object.entries(activeOnAttributes).forEach(([fieldKey, attributesMap]) => {
if (!areActive) {
return;
}

const field = (record.fields ?? {})[fieldKey] ?? null;
if (!field || isEmpty(attributesMap)) {
return;
}

Object.entries(attributesMap).forEach(([attributeKey, activeValues]) => {
if (!areActive) {
return;
}

const attribute = (field.attributes ?? {})[attributeKey] ?? null;
if (!attribute || isEmpty(activeValues)) {
return;
}

areActive = this.isValueActive(record, attribute, activeValues);
});
});

return areActive;
}

/**
* Is value active
*
* @param {Record} record Record
* @param {Field | FieldAttribute} value Value
* @param {Array | any} activeValueOrValues Active Value Or Values
* @returns {boolean} true if is value active
*/
protected isValueActive(
record: Record,
value: Field | FieldAttribute,
activeValueOrValues: string | LogicRuleValues | Array<string | LogicRuleValues>
): boolean {
const activeValues = isArray(activeValueOrValues) ? activeValueOrValues : [activeValueOrValues];

const toCompareValueList = !isEmpty(value.valueList)
? value.valueList
: [value.value];

return activeValues.some(activeValue => {
if (typeof activeValue === 'string') {
return toCompareValueList.some(toCompareValue => activeValue === toCompareValue);
}

const operatorKey = activeValue.operator ?? '';
const operator = this.operatorManager.get(operatorKey);
if (!operator) {
console.warn(`ActiveLogicChecker.isValueActive: Operator: '${operatorKey}' not found.`);
}
return operator?.run(record, value, activeValue);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface AsyncActionInput {
modalRecord?: Record;
record?: Record;

[key: string]: any
[key: string]: any;
}

@Injectable({
Expand Down Expand Up @@ -81,11 +81,17 @@ export class AsyncActionService {
* Send action request
*
* @param {string} actionName to submit
* @param {string} data to send
* @param {object} data to send
* @param {string} presetHandlerKey to use
* @returns {object} Observable<Process>
* @param {string[]} parentHandlers Parent Handlers
* @returns {Observable<Process>} Observable<Process>
*/
public run(actionName: string, data: AsyncActionInput, presetHandlerKey: string = null): Observable<Process> {
public run(
actionName: string,
data: AsyncActionInput,
presetHandlerKey: string = null,
parentHandlers: string[] = []
): Observable<Process> {
const options = {
...data
};
Expand All @@ -106,7 +112,7 @@ export class AsyncActionService {

if (process.messages) {
process.messages.forEach(message => {
if(!!message) {
if(message) {
this.message[handler](message);
}
});
Expand All @@ -124,6 +130,10 @@ export class AsyncActionService {

const actionHandler: AsyncActionHandler = this.actions[actionHandlerKey];

if (parentHandlers.includes(actionHandlerKey)) {
return;
}

if (!actionHandler) {
this.message.addDangerMessageByKey('LBL_MISSING_HANDLER');
return;
Expand All @@ -133,7 +143,7 @@ export class AsyncActionService {

}),
catchError(err => {
const errorMessage = err?.message ?? ''
const errorMessage = err?.message ?? '';

if (errorMessage === 'Access Denied.') {
this.appStateStore.updateLoading(actionName, false);
Expand Down
Loading

0 comments on commit 00278c3

Please sign in to comment.