diff --git a/messages/resources.json b/messages/resources.json index da4eefda..c29cf920 100644 --- a/messages/resources.json +++ b/messages/resources.json @@ -191,6 +191,7 @@ "writingToFile": "{%s} Creating the file %s ...", "nothingUpdated": "Nothing was updated.", "skippedUpdatesWarning": "{%s} %s target records remained untouched, since they do not differ from the corresponding source records.", + "skippedSourceRecordsFilterWarning": "One 'sourceRecordsFilter' could not be applied: %s.", "skippedTargetRecordsFilterWarning": "One 'targetRecordsFilter' could not be applied: %s.", "missingParentLookupsPrompt": "{%s} %s missing parent lookup records were found. See %s file for the details.", "updatingSummary": "Data processing summary.", diff --git a/src/addons/modules/sfdmu-run/custom-addons/package/ISfdmuRunCustomAddonScriptObject.ts b/src/addons/modules/sfdmu-run/custom-addons/package/ISfdmuRunCustomAddonScriptObject.ts index 7f6d98b2..5209c1a0 100644 --- a/src/addons/modules/sfdmu-run/custom-addons/package/ISfdmuRunCustomAddonScriptObject.ts +++ b/src/addons/modules/sfdmu-run/custom-addons/package/ISfdmuRunCustomAddonScriptObject.ts @@ -30,6 +30,7 @@ export default interface ISfdmuRunCustomAddonScriptObject { hardDelete?: boolean; updateWithMockData?: boolean; //mockCSVData?: boolean; + sourceRecordsFilter?: string; targetRecordsFilter?: string; excluded?: boolean; useCSVValuesMapping?: boolean; diff --git a/src/modules/components/common_components/common.ts b/src/modules/components/common_components/common.ts index 1df8a33c..92b20dbd 100644 --- a/src/modules/components/common_components/common.ts +++ b/src/modules/components/common_components/common.ts @@ -1860,5 +1860,30 @@ export class Common { return new Promise(resolve => setTimeout(resolve, time)); } + private static wrapWhereClauseInParenthesis(clause: WhereClause): { beginClause: WhereClause, endClause: WhereClause } { + const clone = JSON.parse(JSON.stringify(clause)) as WhereClause; + clone.left.openParen = (clone.left.openParen ?? 0) + 1 + let current = clone; + while (current.right) { + current = current.right; + } + current.left.closeParen = (current.left.closeParen || 0) + 1 + return { beginClause: clone, endClause: current }; + } + + static mergeWhereClauses( + where1?: WhereClause, + where2?: WhereClause, + operator: LogicalOperator = 'AND', + ): WhereClause | undefined { + if (!where1 || !where2) return where1 || where2; + const { beginClause: wrappedWhere1, endClause: endClause1 } = Common.wrapWhereClauseInParenthesis(where1); + const { beginClause: wrappedWhere2 } = Common.wrapWhereClauseInParenthesis(where2); + + endClause1.operator = operator; + endClause1.right = wrappedWhere2; + + return wrappedWhere1; + } } diff --git a/src/modules/components/common_components/logger.ts b/src/modules/components/common_components/logger.ts index da2e1ca9..295d4b15 100644 --- a/src/modules/components/common_components/logger.ts +++ b/src/modules/components/common_components/logger.ts @@ -212,6 +212,7 @@ export enum RESOURCES { writingToFile = "writingToFile", nothingUpdated = "nothingUpdated", skippedUpdatesWarning = "skippedUpdatesWarning", + skippedSourceRecordsFilterWarning = "skippedSourceRecordsFilterWarning", skippedTargetRecordsFilterWarning = "skippedTargetRecordsFilterWarning", missingParentLookupsPrompt = "missingParentLookupsPrompt", updatingSummary = "updatingSummary", diff --git a/src/modules/models/job_models/migrationJobTask.ts b/src/modules/models/job_models/migrationJobTask.ts index d9d86bad..bc89b08e 100644 --- a/src/modules/models/job_models/migrationJobTask.ts +++ b/src/modules/models/job_models/migrationJobTask.ts @@ -637,6 +637,14 @@ export default class MigrationJobTask { if (isTargetQuery) { // Fix target query ___filterTargetQuery(tempQuery); + } else if (this.scriptObject.sourceRecordsFilter) { + // Add any extra filter conditions to the source query + try { + const additionalWhereClause = parseQuery(`SELECT Id FROM ${this.sObjectName} WHERE ${this.scriptObject.sourceRecordsFilter}`).where; + tempQuery.where = Common.mergeWhereClauses(tempQuery.where, additionalWhereClause); + } catch (ex) { + self.logger.warn(RESOURCES.skippedSourceRecordsFilterWarning, ex.message); + } } let query = composeQuery(tempQuery); diff --git a/src/modules/models/script_models/scriptObject.ts b/src/modules/models/script_models/scriptObject.ts index 29b4bc60..fd9dae67 100644 --- a/src/modules/models/script_models/scriptObject.ts +++ b/src/modules/models/script_models/scriptObject.ts @@ -78,6 +78,7 @@ export default class ScriptObject implements ISfdmuRunScriptObject { hardDelete: boolean = false; updateWithMockData: boolean = false; //mockCSVData: boolean = false; + sourceRecordsFilter: string = ""; targetRecordsFilter: string = ""; excluded: boolean = false; useCSVValuesMapping: boolean = false;