cht-conf 6.0.1 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/fn/delete-contacts.js +1 -1
- package/src/fn/merge-contacts.js +5 -0
- package/src/lib/forms-utils.js +7 -0
- package/src/lib/hierarchy-operations/delete-hierarchy.js +1 -1
- package/src/lib/hierarchy-operations/hierarchy-data-source.js +7 -5
- package/src/lib/hierarchy-operations/index.js +71 -21
- package/src/lib/hierarchy-operations/lineage-constraints.js +46 -22
- package/src/lib/upload-forms.js +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [6.1.0](https://github.com/medic/cht-conf/compare/v6.0.2...v6.1.0) (2026-04-02)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **#653:** add --merge-primary-contacts flag for merge-contacts action ([#654](https://github.com/medic/cht-conf/issues/654)) ([def533d](https://github.com/medic/cht-conf/commit/def533dbca20abca8303aea7f314921c6a1c166b)), closes [#653](https://github.com/medic/cht-conf/issues/653)
|
|
7
|
+
|
|
8
|
+
## [6.0.2](https://github.com/medic/cht-conf/compare/v6.0.1...v6.0.2) (2026-01-16)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **#787:** upload forms with correct `internalId` property ([#788](https://github.com/medic/cht-conf/issues/788)) ([3a28dce](https://github.com/medic/cht-conf/commit/3a28dce3499c7cc2aac7ad5c8a7327303288986f)), closes [#787](https://github.com/medic/cht-conf/issues/787)
|
|
14
|
+
|
|
1
15
|
## [6.0.1](https://github.com/medic/cht-conf/compare/v6.0.0...v6.0.1) (2026-01-14)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -57,7 +57,7 @@ ${bold('OPTIONS')}
|
|
|
57
57
|
A comma delimited list of ids of contacts to be deleted.
|
|
58
58
|
|
|
59
59
|
--disable-users
|
|
60
|
-
When flag is present, users at any deleted place will be permanently disabled.
|
|
60
|
+
When flag is present, users at any deleted place will be updated and may be permanently disabled. Supported by CHT Core 4.7 and above.
|
|
61
61
|
|
|
62
62
|
--docDirectoryPath=<path to stage docs>
|
|
63
63
|
Specifies the folder used to store the documents representing the changes in hierarchy.
|
package/src/fn/merge-contacts.js
CHANGED
|
@@ -15,6 +15,7 @@ module.exports = {
|
|
|
15
15
|
const options = {
|
|
16
16
|
disableUsers: args.disableUsers,
|
|
17
17
|
docDirectoryPath: args.docDirectoryPath,
|
|
18
|
+
mergePrimaryContacts: args.mergePrimaryContacts,
|
|
18
19
|
force: args.force,
|
|
19
20
|
};
|
|
20
21
|
return HierarchyOperations(db, options).merge(args.sourceIds, args.destinationId);
|
|
@@ -50,6 +51,7 @@ const parseExtraArgs = (projectDir, extraArgs = []) => {
|
|
|
50
51
|
sourceIds,
|
|
51
52
|
disableUsers: !!args['disable-users'],
|
|
52
53
|
docDirectoryPath: path.resolve(projectDir, args.docDirectoryPath || 'json_docs'),
|
|
54
|
+
mergePrimaryContacts: !!args['merge-primary-contacts'],
|
|
53
55
|
force: !!args.force,
|
|
54
56
|
};
|
|
55
57
|
};
|
|
@@ -75,6 +77,9 @@ ${bold('OPTIONS')}
|
|
|
75
77
|
--disable-users
|
|
76
78
|
When flag is present, users at any deleted place will be updated and may be permanently disabled. Supported by CHT Core 4.7 and above.
|
|
77
79
|
|
|
80
|
+
--merge-primary-contacts
|
|
81
|
+
When flag is present, the primary contacts for all the top-level places will also be merged into a single resulting contact.
|
|
82
|
+
|
|
78
83
|
--docDirectoryPath=<path to stage docs>
|
|
79
84
|
Specifies the folder used to store the documents representing the changes in hierarchy.
|
|
80
85
|
`);
|
package/src/lib/forms-utils.js
CHANGED
|
@@ -127,6 +127,13 @@ module.exports = {
|
|
|
127
127
|
readTitleFrom: xml =>
|
|
128
128
|
xml.substring(xml.indexOf('<h:title>') + 9, xml.indexOf('</h:title>')),
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Get the ID of the form
|
|
132
|
+
* @param {string} xml the XML string
|
|
133
|
+
* @returns {string}
|
|
134
|
+
*/
|
|
135
|
+
readIdFrom: xml => /<model.*?>[^]*?<instance>[^]*?id="([^"]*)"[^]*?<\/instance>[^]*?<\/model>/.exec(xml)?.[1],
|
|
136
|
+
|
|
130
137
|
/**
|
|
131
138
|
* Escape whitespaces in a path.
|
|
132
139
|
* @param {string} path the path string
|
|
@@ -34,7 +34,7 @@ async function deleteReportsForContact(db, options, contact) {
|
|
|
34
34
|
let skip = 0;
|
|
35
35
|
let reportBatch;
|
|
36
36
|
do {
|
|
37
|
-
reportBatch = await DataSource.getReportsForContacts(db, [], contact._id, skip);
|
|
37
|
+
reportBatch = await DataSource.getReportsForContacts(db, [], [contact._id], skip);
|
|
38
38
|
|
|
39
39
|
for (const report of reportBatch) {
|
|
40
40
|
JsDocs.deleteDoc(options, report);
|
|
@@ -6,6 +6,7 @@ const api = require('../api');
|
|
|
6
6
|
const HIERARCHY_ROOT = 'root';
|
|
7
7
|
const BATCH_SIZE = 10000;
|
|
8
8
|
const NOUVEAU_MIN_VERSION = '5.0.0';
|
|
9
|
+
const SUBJECT_IDS = ['patient_id', 'patient_uuid', 'place_id', 'place_uuid'];
|
|
9
10
|
|
|
10
11
|
/*
|
|
11
12
|
Fetches all of the documents associated with the "contactIds" and confirms they exist.
|
|
@@ -84,17 +85,17 @@ const fetchReportsByCreator = async (db, createdByIds, skip) => {
|
|
|
84
85
|
return await getFromDbView(db, 'medic-client/reports_by_freetext', createdByKeys, skip);
|
|
85
86
|
};
|
|
86
87
|
|
|
87
|
-
const fetchReportsBySubject = async (db,
|
|
88
|
-
if (!
|
|
88
|
+
const fetchReportsBySubject = async (db, createdAtIds, skip) => {
|
|
89
|
+
if (!createdAtIds || createdAtIds.length <= 0) {
|
|
89
90
|
return [];
|
|
90
91
|
}
|
|
91
|
-
return await getFromDbView(db, 'medic-client/reports_by_subject',
|
|
92
|
+
return await getFromDbView(db, 'medic-client/reports_by_subject', createdAtIds, skip);
|
|
92
93
|
};
|
|
93
94
|
|
|
94
|
-
const getReportsForContacts = async (db, createdByIds,
|
|
95
|
+
const getReportsForContacts = async (db, createdByIds, createdAtIds, skip) => {
|
|
95
96
|
const [creatorReports, subjectReports] = await Promise.all([
|
|
96
97
|
fetchReportsByCreator(db, createdByIds, skip),
|
|
97
|
-
fetchReportsBySubject(db,
|
|
98
|
+
fetchReportsBySubject(db, createdAtIds, skip)
|
|
98
99
|
]);
|
|
99
100
|
|
|
100
101
|
const allRows = [...creatorReports, ...subjectReports];
|
|
@@ -124,6 +125,7 @@ async function getAncestorsOf(db, contactDoc) {
|
|
|
124
125
|
module.exports = {
|
|
125
126
|
BATCH_SIZE,
|
|
126
127
|
HIERARCHY_ROOT,
|
|
128
|
+
SUBJECT_IDS,
|
|
127
129
|
getAncestorsOf,
|
|
128
130
|
getContactWithDescendants,
|
|
129
131
|
getContact,
|
|
@@ -19,27 +19,27 @@ async function moveHierarchy(db, options, sourceIds, destinationId) {
|
|
|
19
19
|
for (const sourceId of sourceIds) {
|
|
20
20
|
const sourceDoc = sourceDocs[sourceId];
|
|
21
21
|
const descendantsAndSelf = await DataSource.getContactWithDescendants(db, sourceId);
|
|
22
|
+
|
|
22
23
|
const moveContext = {
|
|
23
24
|
sourceId,
|
|
24
25
|
destinationId,
|
|
25
26
|
descendantsAndSelf,
|
|
26
27
|
replacementLineage,
|
|
27
28
|
merge: !!options.merge,
|
|
29
|
+
disableUsers: !!options.disableUsers,
|
|
30
|
+
mergePrimaryContacts: !!options.mergePrimaryContacts,
|
|
31
|
+
sourcePrimaryContactId: getPrimaryContactId(sourceDoc),
|
|
32
|
+
destinationPrimaryContactId: getPrimaryContactId(destinationDoc),
|
|
28
33
|
};
|
|
29
34
|
|
|
30
35
|
await constraints.assertNoPrimaryContactViolations(sourceDoc, destinationDoc, descendantsAndSelf);
|
|
31
36
|
|
|
32
|
-
if (options.merge) {
|
|
33
|
-
const toDeleteUsers = options.disableUsers && constraints.isPlace(sourceDoc);
|
|
34
|
-
JsDocs.deleteDoc(options, sourceDoc, toDeleteUsers);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
37
|
const prettyPrintDocument = doc => `'${doc.name}' (${doc._id})`;
|
|
38
38
|
trace(
|
|
39
39
|
`Considering lineage updates to ${descendantsAndSelf.length} descendant(s) of contact `
|
|
40
40
|
+ `${prettyPrintDocument(sourceDoc)}.`
|
|
41
41
|
);
|
|
42
|
-
const updatedDescendants =
|
|
42
|
+
const updatedDescendants = updateContacts(moveContext, constraints);
|
|
43
43
|
|
|
44
44
|
const ancestors = await DataSource.getAncestorsOf(db, sourceDoc);
|
|
45
45
|
trace(
|
|
@@ -68,6 +68,10 @@ async function moveHierarchy(db, options, sourceIds, destinationId) {
|
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function getPrimaryContactId(doc) {
|
|
72
|
+
return typeof doc?.contact === 'string' ? doc.contact : doc?.contact?._id;
|
|
73
|
+
}
|
|
74
|
+
|
|
71
75
|
async function updateReports(db, options, moveContext) {
|
|
72
76
|
const descendantIds = moveContext.descendantsAndSelf.map(contact => contact._id);
|
|
73
77
|
|
|
@@ -75,8 +79,8 @@ async function updateReports(db, options, moveContext) {
|
|
|
75
79
|
let reportDocsBatch;
|
|
76
80
|
do {
|
|
77
81
|
info(`Processing ${skip} to ${skip + DataSource.BATCH_SIZE} report docs`);
|
|
78
|
-
const
|
|
79
|
-
reportDocsBatch = await DataSource.getReportsForContacts(db, descendantIds,
|
|
82
|
+
const createdAtIds = getReportsCreatedAtIds(moveContext);
|
|
83
|
+
reportDocsBatch = await DataSource.getReportsForContacts(db, descendantIds, createdAtIds, skip);
|
|
80
84
|
|
|
81
85
|
const lineageUpdates = replaceLineageOfReportCreator(reportDocsBatch, moveContext);
|
|
82
86
|
const reassignUpdates = reassignReports(reportDocsBatch, moveContext);
|
|
@@ -90,24 +94,53 @@ async function updateReports(db, options, moveContext) {
|
|
|
90
94
|
return skip;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
|
-
function
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
function getReportsCreatedAtIds(moveContext) {
|
|
98
|
+
const result = [];
|
|
99
|
+
if (moveContext.merge) {
|
|
100
|
+
result.push(moveContext.sourceId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (moveContext.mergePrimaryContacts && moveContext.sourcePrimaryContactId) {
|
|
104
|
+
result.push(moveContext.sourcePrimaryContactId);
|
|
105
|
+
}
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function reassignReportSubjects(report, moveContext) {
|
|
111
|
+
let updated = false;
|
|
112
|
+
for (const subjectId of DataSource.SUBJECT_IDS) {
|
|
113
|
+
updated = updated || reassignSingleReport(report, subjectId, moveContext.sourceId, moveContext.destinationId);
|
|
114
|
+
|
|
115
|
+
if (
|
|
116
|
+
moveContext.mergePrimaryContacts &&
|
|
117
|
+
moveContext.sourcePrimaryContactId &&
|
|
118
|
+
moveContext.destinationPrimaryContactId
|
|
119
|
+
) {
|
|
120
|
+
updated = updated || reassignSingleReport(
|
|
121
|
+
report, subjectId, moveContext.sourcePrimaryContactId, moveContext.destinationPrimaryContactId
|
|
122
|
+
);
|
|
105
123
|
}
|
|
106
124
|
}
|
|
107
125
|
|
|
108
126
|
return updated;
|
|
109
127
|
}
|
|
110
128
|
|
|
129
|
+
function reassignSingleReport(report, subjectId, matchId, resultingId) {
|
|
130
|
+
let result = false;
|
|
131
|
+
if (report[subjectId] === matchId) {
|
|
132
|
+
report[subjectId] = resultingId;
|
|
133
|
+
result = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (report.fields[subjectId] === matchId) {
|
|
137
|
+
report.fields[subjectId] = resultingId;
|
|
138
|
+
result = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
111
144
|
function reassignReports(reports, moveContext) {
|
|
112
145
|
const updated = new Set();
|
|
113
146
|
if (!moveContext.merge) {
|
|
@@ -179,9 +212,26 @@ function replaceLineageInSingleContact(doc, moveContext) {
|
|
|
179
212
|
}
|
|
180
213
|
}
|
|
181
214
|
|
|
182
|
-
function
|
|
215
|
+
function updateContacts(moveContext, constraints) {
|
|
183
216
|
return moveContext.descendantsAndSelf
|
|
184
|
-
.map(descendant =>
|
|
217
|
+
.map(descendant => {
|
|
218
|
+
const deleteSource = moveContext.merge && descendant._id === moveContext.sourceId;
|
|
219
|
+
const deletePrimaryContact = moveContext.mergePrimaryContacts
|
|
220
|
+
&& descendant._id === moveContext.sourcePrimaryContactId
|
|
221
|
+
&& moveContext.destinationPrimaryContactId;
|
|
222
|
+
|
|
223
|
+
if (deleteSource || deletePrimaryContact) {
|
|
224
|
+
const toDeleteUsers = moveContext.disableUsers && constraints.isPlace(descendant);
|
|
225
|
+
return {
|
|
226
|
+
_id: descendant._id,
|
|
227
|
+
_rev: descendant._rev,
|
|
228
|
+
_deleted: true,
|
|
229
|
+
cht_disable_linked_users: !!toDeleteUsers,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return replaceLineageInSingleContact(descendant, moveContext);
|
|
234
|
+
})
|
|
185
235
|
.filter(Boolean);
|
|
186
236
|
}
|
|
187
237
|
|
|
@@ -9,13 +9,8 @@ module.exports = async (db, options) => {
|
|
|
9
9
|
|
|
10
10
|
return {
|
|
11
11
|
assertNoPrimaryContactViolations: async (sourceDoc, destinationDoc, descendantDocs) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
throw Error(
|
|
15
|
-
`Cannot remove contact '${invalidPrimaryContactDoc?.name}' (${invalidPrimaryContactDoc?._id}) from the `
|
|
16
|
-
+ 'hierarchy for which they are a primary contact.'
|
|
17
|
-
);
|
|
18
|
-
}
|
|
12
|
+
await assertOnPrimaryContactRemoval(db, sourceDoc, destinationDoc, descendantDocs);
|
|
13
|
+
await assertSourcePrimaryContactType(db, contactTypeInfo, sourceDoc);
|
|
19
14
|
},
|
|
20
15
|
assertNoHierarchyErrors: (sourceDocs, destinationDoc) => {
|
|
21
16
|
|
|
@@ -30,7 +25,7 @@ module.exports = async (db, options) => {
|
|
|
30
25
|
throw Error(`Hierarchy Constraints: ${hierarchyError}`);
|
|
31
26
|
}
|
|
32
27
|
});
|
|
33
|
-
|
|
28
|
+
|
|
34
29
|
/*
|
|
35
30
|
* It is nice that the tool can move lists of contacts as one operation, but strange things happen when two
|
|
36
31
|
* contactIds are in the same lineage. For example, moving a district_hospital and moving a contact under that
|
|
@@ -47,13 +42,16 @@ module.exports = async (db, options) => {
|
|
|
47
42
|
});
|
|
48
43
|
},
|
|
49
44
|
|
|
50
|
-
isPlace: (contact) =>
|
|
51
|
-
const contactType = getContactType(contact);
|
|
52
|
-
return !contactTypeInfo[contactType]?.person;
|
|
53
|
-
},
|
|
45
|
+
isPlace: (contact) => isPlace(contactTypeInfo, contact),
|
|
54
46
|
};
|
|
55
47
|
};
|
|
56
48
|
|
|
49
|
+
function isPlace(contactTypeInfo, contact) {
|
|
50
|
+
const contactType = getContactType(contact);
|
|
51
|
+
const isPerson = contactTypeInfo[contactType]?.person || false;
|
|
52
|
+
return !isPerson;
|
|
53
|
+
}
|
|
54
|
+
|
|
57
55
|
/*
|
|
58
56
|
Enforce the list of allowed parents for each contact type
|
|
59
57
|
Ensure we are not creating a circular hierarchy
|
|
@@ -133,24 +131,50 @@ A place's primary contact must be a descendant of that place.
|
|
|
133
131
|
1. Check to see which part of the contact's lineage will be removed
|
|
134
132
|
2. For each removed part of the contact's lineage, confirm that place's primary contact isn't being removed.
|
|
135
133
|
*/
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
const
|
|
134
|
+
async function assertOnPrimaryContactRemoval(db, sourceDoc, destinationDoc, descendantDocs) {
|
|
135
|
+
const sourceLineageIds = lineageManipulation.pluckIdsFromLineage(sourceDoc?.parent);
|
|
136
|
+
const destinationLineageIds = lineageManipulation.pluckIdsFromLineage(destinationDoc);
|
|
139
137
|
|
|
140
|
-
const docIdsRemovedFromContactLineage =
|
|
138
|
+
const docIdsRemovedFromContactLineage = sourceLineageIds.filter(value => !destinationLineageIds.includes(value));
|
|
141
139
|
const docsRemovedFromContactLineage = await db.allDocs({
|
|
142
140
|
keys: docIdsRemovedFromContactLineage,
|
|
143
141
|
include_docs: true,
|
|
144
142
|
});
|
|
145
143
|
|
|
146
|
-
const primaryContactIds = docsRemovedFromContactLineage.rows
|
|
144
|
+
const primaryContactIds = new Set(docsRemovedFromContactLineage.rows
|
|
147
145
|
.map(row => row?.doc?.contact?._id)
|
|
148
|
-
.filter(Boolean);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
.filter(Boolean));
|
|
147
|
+
|
|
148
|
+
const invalidPrimaryContactDoc = descendantDocs.find(
|
|
149
|
+
descendant => primaryContactIds.has(descendant._id)
|
|
150
|
+
);
|
|
151
|
+
if (invalidPrimaryContactDoc) {
|
|
152
|
+
throw new Error('Cannot remove contact '+
|
|
153
|
+
`'${invalidPrimaryContactDoc?.name}' (${invalidPrimaryContactDoc?._id}) ` +
|
|
154
|
+
'from the hierarchy for which they are a primary contact.');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function assertSourcePrimaryContactType(db, contactTypeInfo, sourceDoc) {
|
|
159
|
+
const sourcePrimaryContactId = getPrimaryContactId(sourceDoc);
|
|
160
|
+
if (!sourcePrimaryContactId) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const sourcePrimaryContactDoc = await db.get(sourcePrimaryContactId);
|
|
165
|
+
const primaryContactIsPlace = isPlace(contactTypeInfo, sourcePrimaryContactDoc);
|
|
166
|
+
if (primaryContactIsPlace) {
|
|
167
|
+
throw new Error(`Source "${sourceDoc._id}" has primary contact "${sourcePrimaryContactId}" which is of type place`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getContactType(doc) {
|
|
172
|
+
return doc?.type === 'contact' ? doc?.contact_type : doc?.type;
|
|
173
|
+
}
|
|
152
174
|
|
|
153
|
-
|
|
175
|
+
function getPrimaryContactId(doc) {
|
|
176
|
+
return typeof doc?.contact === 'string' ? doc.contact : doc?.contact?._id;
|
|
177
|
+
}
|
|
154
178
|
|
|
155
179
|
async function fetchContactTypeInfo(db) {
|
|
156
180
|
try {
|
package/src/lib/upload-forms.js
CHANGED
|
@@ -10,7 +10,7 @@ const warnUploadOverwrite = require('./warn-upload-overwrite');
|
|
|
10
10
|
const {
|
|
11
11
|
getFormDir,
|
|
12
12
|
getFormFilePaths,
|
|
13
|
-
readTitleFrom,
|
|
13
|
+
readTitleFrom, readIdFrom,
|
|
14
14
|
} = require('./forms-utils');
|
|
15
15
|
|
|
16
16
|
const SUPPORTED_PROPERTIES = ['context', 'icon', 'title', 'xml2sms', 'subject_key', 'hidden_fields'];
|
|
@@ -59,7 +59,7 @@ const execute = async (projectDir, subDirectory, options) => {
|
|
|
59
59
|
log.info(`Preparing form for upload: ${fileName}…`);
|
|
60
60
|
|
|
61
61
|
const { baseFileName, mediaDir, xformPath, filePath } = getFormFilePaths(formsDir, fileName);
|
|
62
|
-
const
|
|
62
|
+
const baseDocId = (options.id_prefix || '') + baseFileName.replaceAll('-', ':');
|
|
63
63
|
|
|
64
64
|
const mediaDirExists = fs.exists(mediaDir);
|
|
65
65
|
if (!mediaDirExists) {
|
|
@@ -74,7 +74,14 @@ const execute = async (projectDir, subDirectory, options) => {
|
|
|
74
74
|
sha256: hashSum.digest('hex'),
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
const
|
|
77
|
+
const internalId = readIdFrom(xml);
|
|
78
|
+
if (internalId.replaceAll('-', ':') !== baseDocId) {
|
|
79
|
+
throw new Error(`The file name for the form [${baseFileName}] does not match the id in the xml [${
|
|
80
|
+
internalId
|
|
81
|
+
}]. Rename the form xlsx/xml files to match the id.`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const docId = `form:${baseDocId}`;
|
|
78
85
|
const doc = {
|
|
79
86
|
_id: docId,
|
|
80
87
|
type: 'form',
|