Skip to content

Commit 50be987

Browse files
author
hknokh2
committed
fix: match relationship externalId values from nested records
- added nested dotted field fallback for org relationship records - fixed Account__r.Name matching for csvfile to org upsert - added unit test for nested target relationship records
1 parent a32719c commit 50be987

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

src/modules/models/job/MigrationJobTask.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,18 +1818,69 @@ export default class MigrationJobTask implements ISFdmuRunCustomAddonTask {
18181818
* @returns Field value or undefined.
18191819
*/
18201820
private _getRecordFieldValue(record: Record<string, unknown>, fieldName: string): string | undefined {
1821-
void this;
18221821
if (!fieldName || !Object.prototype.hasOwnProperty.call(record, fieldName)) {
1822+
return this._getNestedRecordFieldValue(record, fieldName);
1823+
}
1824+
return this._normalizeRecordFieldValue(record[fieldName]);
1825+
}
1826+
1827+
/**
1828+
* Reads a nested record field value using a dotted relationship path.
1829+
*
1830+
* @param record - Record to inspect.
1831+
* @param fieldName - Dotted field path.
1832+
* @returns Field value or undefined.
1833+
*/
1834+
private _getNestedRecordFieldValue(record: Record<string, unknown>, fieldName: string): string | undefined {
1835+
if (!fieldName.includes('.')) {
1836+
return undefined;
1837+
}
1838+
1839+
let currentValue: unknown = record;
1840+
const pathParts = fieldName
1841+
.split('.')
1842+
.map((part) => part.trim())
1843+
.filter((part) => part.length > 0);
1844+
if (pathParts.length < 2) {
18231845
return undefined;
18241846
}
1825-
const rawValue = record[fieldName];
1847+
1848+
for (const pathPart of pathParts) {
1849+
if (!this._isRecordLike(currentValue) || !Object.prototype.hasOwnProperty.call(currentValue, pathPart)) {
1850+
return undefined;
1851+
}
1852+
currentValue = currentValue[pathPart];
1853+
}
1854+
1855+
return this._normalizeRecordFieldValue(currentValue);
1856+
}
1857+
1858+
/**
1859+
* Converts a raw record value to a non-empty string.
1860+
*
1861+
* @param rawValue - Raw value.
1862+
* @returns Normalized text value or undefined.
1863+
*/
1864+
private _normalizeRecordFieldValue(rawValue: unknown): string | undefined {
1865+
void this;
18261866
if (rawValue === null || typeof rawValue === 'undefined') {
18271867
return undefined;
18281868
}
18291869
const textValue = String(rawValue);
18301870
return textValue.length > 0 ? textValue : undefined;
18311871
}
18321872

1873+
/**
1874+
* Determines whether a value can be traversed as a record object.
1875+
*
1876+
* @param value - Value to inspect.
1877+
* @returns True when the value is a plain record-like object.
1878+
*/
1879+
private _isRecordLike(value: unknown): value is Record<string, unknown> {
1880+
void this;
1881+
return typeof value === 'object' && value !== null && !Array.isArray(value);
1882+
}
1883+
18331884
/**
18341885
* Reads the record Id value as a string.
18351886
*

test/modules/job/migration-job-task.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,31 @@ const createTaskFixture = (): TaskFixtureType => {
7272

7373
const createMap = (pairs: Array<[string, string]>): LookupIdMapType => new Map(pairs);
7474

75+
describe('MigrationJobTask relationship external id matching', () => {
76+
it('links target records by nested relationship external id values', () => {
77+
const { task } = createTaskFixture();
78+
task.scriptObject.externalId = 'Account__r.Name';
79+
80+
const sourceRecord: Record<string, unknown> = {
81+
Id: 'a1B000000000001AAA',
82+
'Account__r.Name': 'Acme Corp',
83+
};
84+
const targetRecord: Record<string, unknown> = {
85+
Id: 'a1B000000000002AAA',
86+
'Account__r': {
87+
Name: 'Acme Corp',
88+
},
89+
};
90+
91+
task.registerRecords([sourceRecord], task.sourceData, false);
92+
task.registerRecords([targetRecord], task.targetData, true);
93+
94+
assert.equal(task.sourceData.extIdToRecordIdMap.get('Acme Corp'), 'a1B000000000001AAA');
95+
assert.equal(task.targetData.extIdToRecordIdMap.get('Acme Corp'), 'a1B000000000002AAA');
96+
assert.equal(task.sourceToTargetRecordMap.get(sourceRecord), targetRecord);
97+
});
98+
});
99+
75100
describe('MigrationJobTask field capability warnings', () => {
76101
it('warns only for explicit update query fields that are not updateable', () => {
77102
const originalLogger = Common.logger;

0 commit comments

Comments
 (0)