@openinc/parse-server-opendash 4.1.1 → 4.1.2
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/dist/featuremap.json +27 -12
- package/dist/features/config/helper/customOptions.js +9 -0
- package/dist/features/config/helper/isFeatureEnabled.d.ts +1 -1
- package/dist/features/config/helper/isFeatureEnabled.js +2 -0
- package/dist/features/meta/index.d.ts +1 -0
- package/dist/features/meta/index.js +5 -0
- package/dist/features/meta/services/registerMetaContext.d.ts +29 -0
- package/dist/features/meta/services/registerMetaContext.js +208 -0
- package/dist/features/permissions/types/Permissions.d.ts +24 -1
- package/dist/features/permissions/types/Permissions.js +34 -0
- package/dist/features/ruleset/applyRuleSet.d.ts +41 -0
- package/dist/features/ruleset/applyRuleSet.js +81 -0
- package/dist/features/schema/helper/defaultHandler.js +13 -0
- package/dist/features/schema/helper/migrationMode.d.ts +26 -0
- package/dist/features/schema/helper/migrationMode.js +48 -0
- package/dist/features/schema/index.d.ts +1 -0
- package/dist/features/schema/index.js +3 -1
- package/dist/features/schema/services/beforeSaveHook.js +21 -0
- package/dist/features/translations/services/initTranslation.js +1 -1
- package/dist/hooks/Chat_Message.d.ts +1 -0
- package/dist/hooks/Chat_Message.js +179 -0
- package/dist/hooks/Meta_Config.js +19 -10
- package/dist/hooks/Meta_Context.d.ts +6 -0
- package/dist/hooks/Meta_Context.js +16 -0
- package/dist/hooks/Meta_Entry.js +20 -10
- package/dist/hooks/Service_Equipment.d.ts +1 -0
- package/dist/hooks/Service_Equipment.js +38 -0
- package/dist/hooks/Service_FormConfig.d.ts +1 -0
- package/dist/hooks/Service_FormConfig.js +17 -0
- package/dist/hooks/Service_Project.d.ts +1 -0
- package/dist/hooks/Service_Project.js +17 -0
- package/dist/hooks/Service_Schedule.d.ts +1 -0
- package/dist/hooks/Service_Schedule.js +24 -0
- package/dist/hooks/Service_Schedule_Execution.d.ts +1 -0
- package/dist/hooks/Service_Schedule_Execution.js +17 -0
- package/dist/hooks/Service_Schedule_Template.d.ts +1 -0
- package/dist/hooks/Service_Schedule_Template.js +17 -0
- package/dist/hooks/Service_Status.d.ts +1 -0
- package/dist/hooks/Service_Status.js +32 -0
- package/dist/hooks/Service_Tag.d.ts +1 -0
- package/dist/hooks/Service_Tag.js +168 -0
- package/dist/hooks/Service_Ticket.d.ts +1 -0
- package/dist/hooks/Service_Ticket.js +433 -0
- package/dist/hooks/Service_TriggerState.d.ts +1 -0
- package/dist/hooks/Service_TriggerState.js +17 -0
- package/dist/hooks/_ChangeLog.js +27 -6
- package/dist/i18n/de-DE/chat.json +18 -0
- package/dist/i18n/de-DE/openservice.json +14 -0
- package/dist/i18n/en-US/chat.json +18 -0
- package/dist/i18n/en-US/openservice.json +14 -0
- package/dist/scripts/backfill-meta-contexts.d.ts +1 -0
- package/dist/scripts/backfill-meta-contexts.js +100 -0
- package/dist/scripts/migration/lib/acl.d.ts +52 -0
- package/dist/scripts/migration/lib/acl.js +79 -0
- package/dist/scripts/migration/lib/articleMeta.d.ts +47 -0
- package/dist/scripts/migration/lib/articleMeta.js +132 -0
- package/dist/scripts/migration/lib/env.d.ts +56 -0
- package/dist/scripts/migration/lib/env.js +106 -0
- package/dist/scripts/migration/lib/latest.d.ts +47 -0
- package/dist/scripts/migration/lib/latest.js +115 -0
- package/dist/scripts/migration/lib/map.d.ts +53 -0
- package/dist/scripts/migration/lib/map.js +113 -0
- package/dist/scripts/migration/lib/material.d.ts +47 -0
- package/dist/scripts/migration/lib/material.js +51 -0
- package/dist/scripts/migration/lib/message.d.ts +78 -0
- package/dist/scripts/migration/lib/message.js +115 -0
- package/dist/scripts/migration/lib/pass.d.ts +67 -0
- package/dist/scripts/migration/lib/pass.js +89 -0
- package/dist/scripts/migration/lib/ptr.d.ts +32 -0
- package/dist/scripts/migration/lib/ptr.js +38 -0
- package/dist/scripts/migration/lib/report.d.ts +41 -0
- package/dist/scripts/migration/lib/report.js +78 -0
- package/dist/scripts/migration/lib/rest.d.ts +47 -0
- package/dist/scripts/migration/lib/rest.js +68 -0
- package/dist/scripts/migration/lib/ticket.d.ts +72 -0
- package/dist/scripts/migration/lib/ticket.js +192 -0
- package/dist/scripts/migration/passes/01-statuses.d.ts +12 -0
- package/dist/scripts/migration/passes/01-statuses.js +122 -0
- package/dist/scripts/migration/passes/02-tags.d.ts +13 -0
- package/dist/scripts/migration/passes/02-tags.js +150 -0
- package/dist/scripts/migration/passes/03-equipment.d.ts +14 -0
- package/dist/scripts/migration/passes/03-equipment.js +176 -0
- package/dist/scripts/migration/passes/04-projects.d.ts +27 -0
- package/dist/scripts/migration/passes/04-projects.js +241 -0
- package/dist/scripts/migration/passes/05-articles.d.ts +25 -0
- package/dist/scripts/migration/passes/05-articles.js +203 -0
- package/dist/scripts/migration/passes/06-formconfigs.d.ts +17 -0
- package/dist/scripts/migration/passes/06-formconfigs.js +166 -0
- package/dist/scripts/migration/passes/07-meta-configs.d.ts +46 -0
- package/dist/scripts/migration/passes/07-meta-configs.js +185 -0
- package/dist/scripts/migration/passes/08-tickets.d.ts +27 -0
- package/dist/scripts/migration/passes/08-tickets.js +353 -0
- package/dist/scripts/migration/passes/09-ticket-meta.d.ts +12 -0
- package/dist/scripts/migration/passes/09-ticket-meta.js +136 -0
- package/dist/scripts/migration/passes/10-chat.d.ts +17 -0
- package/dist/scripts/migration/passes/10-chat.js +230 -0
- package/dist/scripts/migration/passes/11-assets.d.ts +17 -0
- package/dist/scripts/migration/passes/11-assets.js +93 -0
- package/dist/scripts/migration/passes/12-history.d.ts +24 -0
- package/dist/scripts/migration/passes/12-history.js +332 -0
- package/dist/scripts/migration/run.d.ts +1 -0
- package/dist/scripts/migration/run.js +148 -0
- package/dist/scripts/migration/verify.d.ts +1 -0
- package/dist/scripts/migration/verify.js +620 -0
- package/dist/scripts/seed-v1-testdata.d.ts +41 -0
- package/dist/scripts/seed-v1-testdata.js +543 -0
- package/dist/types/BDE_Article.d.ts +3 -0
- package/dist/types/BDE_Article.js +6 -0
- package/dist/types/Chat_Message.d.ts +49 -0
- package/dist/types/Chat_Message.js +87 -0
- package/dist/types/MES_RessourceOverview.d.ts +38 -0
- package/dist/types/MES_RessourceOverview.js +63 -0
- package/dist/types/Meta_Context.d.ts +43 -0
- package/dist/types/Meta_Context.js +75 -0
- package/dist/types/Service_Equipment.d.ts +33 -0
- package/dist/types/Service_Equipment.js +57 -0
- package/dist/types/Service_FormConfig.d.ts +33 -0
- package/dist/types/Service_FormConfig.js +57 -0
- package/dist/types/Service_MeterReading.d.ts +35 -0
- package/dist/types/Service_MeterReading.js +57 -0
- package/dist/types/Service_Project.d.ts +53 -0
- package/dist/types/Service_Project.js +93 -0
- package/dist/types/Service_Schedule.d.ts +66 -0
- package/dist/types/Service_Schedule.js +117 -0
- package/dist/types/Service_Schedule_Execution.d.ts +38 -0
- package/dist/types/Service_Schedule_Execution.js +63 -0
- package/dist/types/Service_Schedule_Template.d.ts +36 -0
- package/dist/types/Service_Schedule_Template.js +60 -0
- package/dist/types/Service_Status.d.ts +30 -0
- package/dist/types/Service_Status.js +51 -0
- package/dist/types/Service_Tag.d.ts +30 -0
- package/dist/types/Service_Tag.js +51 -0
- package/dist/types/Service_Ticket.d.ts +92 -0
- package/dist/types/Service_Ticket.js +165 -0
- package/dist/types/Service_TicketCounter.d.ts +18 -0
- package/dist/types/Service_TicketCounter.js +27 -0
- package/dist/types/Service_TriggerState.d.ts +31 -0
- package/dist/types/Service_TriggerState.js +51 -0
- package/dist/types/_Session.d.ts +0 -1
- package/dist/types/_User.d.ts +2 -6
- package/dist/types/custom/MetaField.d.ts +9 -0
- package/dist/types/custom/TicketMaterial.d.ts +32 -0
- package/dist/types/custom/TicketMaterial.js +2 -0
- package/dist/types/index.d.ts +30 -0
- package/dist/types/index.js +33 -3
- package/package.json +6 -3
- package/schema/BDE_Article.json +3 -0
- package/schema/Changelog.json +3 -6
- package/schema/Chat_Message.json +88 -0
- package/schema/Meta_Context.json +78 -0
- package/schema/Service_Equipment.json +55 -0
- package/schema/Service_FormConfig.json +66 -0
- package/schema/Service_MeterReading.json +54 -0
- package/schema/Service_MigrationMap.json +55 -0
- package/schema/Service_Project.json +77 -0
- package/schema/Service_Schedule.json +101 -0
- package/schema/Service_Schedule_Execution.json +58 -0
- package/schema/Service_Schedule_Template.json +73 -0
- package/schema/Service_Status.json +50 -0
- package/schema/Service_Tag.json +50 -0
- package/schema/Service_Ticket.json +114 -0
- package/schema/Service_TicketCounter.json +38 -0
- package/schema/Service_TriggerState.json +50 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TicketNumberer = void 0;
|
|
7
|
+
exports.loadRoleNames = loadRoleNames;
|
|
8
|
+
exports.ticketAcl = ticketAcl;
|
|
9
|
+
exports.loadTicketAcls = loadTicketAcls;
|
|
10
|
+
const node_1 = __importDefault(require("parse/node"));
|
|
11
|
+
const acl_js_1 = require("./acl.js");
|
|
12
|
+
const pass_js_1 = require("./pass.js");
|
|
13
|
+
/**
|
|
14
|
+
* Ticket numbering and ACLs, which the migration has to do itself.
|
|
15
|
+
*
|
|
16
|
+
* Normally `beforeSave(OD3_Service_Ticket)` handles both. But migration mode
|
|
17
|
+
* suppresses beforeSave registration entirely — the only way Parse will honour a
|
|
18
|
+
* historical `createdAt`, because `RestWrite` replaces the write payload with the
|
|
19
|
+
* trigger's `Parse.Object` round-trip, which discards the field (see
|
|
20
|
+
* `features/schema/services/beforeSaveHook.ts`). So the two things that hook did
|
|
21
|
+
* are reimplemented here, deliberately mirroring
|
|
22
|
+
* `hooks/Service_Ticket.ts` — keep them in sync.
|
|
23
|
+
*/
|
|
24
|
+
// ── Numbering ──────────────────────────────────────────────────────────────
|
|
25
|
+
const COUNTER_CLASS = "OD3_Service_TicketCounter";
|
|
26
|
+
/**
|
|
27
|
+
* Hands out `Ticket.number` the way the backend hook would.
|
|
28
|
+
*
|
|
29
|
+
* Per-project when the ticket has one (`Project.ticketCounter`), otherwise
|
|
30
|
+
* per-tenant (`OD3_Service_TicketCounter.value`).
|
|
31
|
+
*
|
|
32
|
+
* Counters are seeded from their **current** stored values and flushed once at the
|
|
33
|
+
* end, rather than incremented per ticket. That is both far fewer round trips and
|
|
34
|
+
* safe for a re-run: an already-migrated ticket keeps the number it has and
|
|
35
|
+
* consumes nothing, so a resumed run continues the sequence instead of restarting
|
|
36
|
+
* it. Safe precisely because the migration is single-threaded — the hook needs
|
|
37
|
+
* atomic `$inc` because concurrent clients race; here nothing else is writing.
|
|
38
|
+
*/
|
|
39
|
+
class TicketNumberer {
|
|
40
|
+
config;
|
|
41
|
+
projectCounters = new Map();
|
|
42
|
+
tenantCounters = new Map();
|
|
43
|
+
tenantCounterIds = new Map();
|
|
44
|
+
dirtyProjects = new Set();
|
|
45
|
+
dirtyTenants = new Set();
|
|
46
|
+
constructor(config) {
|
|
47
|
+
this.config = config;
|
|
48
|
+
}
|
|
49
|
+
/** Read the current counter values so numbering continues rather than restarts. */
|
|
50
|
+
async load() {
|
|
51
|
+
for await (const page of (0, pass_js_1.pages)(() => new node_1.default.Query("OD3_Service_Project"), this.config.batchSize)) {
|
|
52
|
+
for (const project of page) {
|
|
53
|
+
if (project.id) {
|
|
54
|
+
this.projectCounters.set(project.id, project.get("ticketCounter") ?? 0);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for await (const page of (0, pass_js_1.pages)(() => new node_1.default.Query(COUNTER_CLASS), this.config.batchSize)) {
|
|
59
|
+
for (const counter of page) {
|
|
60
|
+
const tenantId = (0, pass_js_1.refId)(counter.get("tenant"));
|
|
61
|
+
if (!tenantId || !counter.id)
|
|
62
|
+
continue;
|
|
63
|
+
this.tenantCounters.set(tenantId, counter.get("value") ?? 0);
|
|
64
|
+
this.tenantCounterIds.set(tenantId, counter.id);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The next number for a ticket, or undefined when there is no scope to count in
|
|
70
|
+
* (no project and no tenant) — matching the hook, which leaves those unnumbered.
|
|
71
|
+
*/
|
|
72
|
+
next(projectId, tenantId) {
|
|
73
|
+
if (projectId) {
|
|
74
|
+
const value = (this.projectCounters.get(projectId) ?? 0) + 1;
|
|
75
|
+
this.projectCounters.set(projectId, value);
|
|
76
|
+
this.dirtyProjects.add(projectId);
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
if (tenantId) {
|
|
80
|
+
const value = (this.tenantCounters.get(tenantId) ?? 0) + 1;
|
|
81
|
+
this.tenantCounters.set(tenantId, value);
|
|
82
|
+
this.dirtyTenants.add(tenantId);
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Persist the counters.
|
|
89
|
+
*
|
|
90
|
+
* Must run even if the pass had per-ticket errors: leaving a counter behind the
|
|
91
|
+
* numbers already handed out would make the *next* run reuse them, producing
|
|
92
|
+
* duplicates that `verify.ts` would then flag.
|
|
93
|
+
*/
|
|
94
|
+
async flush() {
|
|
95
|
+
let projects = 0;
|
|
96
|
+
for (const projectId of this.dirtyProjects) {
|
|
97
|
+
const project = new node_1.default.Object("OD3_Service_Project");
|
|
98
|
+
project.id = projectId;
|
|
99
|
+
project.set("ticketCounter", this.projectCounters.get(projectId) ?? 0);
|
|
100
|
+
await project.save(null, acl_js_1.MIGRATION_SAVE);
|
|
101
|
+
projects += 1;
|
|
102
|
+
}
|
|
103
|
+
let tenants = 0;
|
|
104
|
+
for (const tenantId of this.dirtyTenants) {
|
|
105
|
+
const value = this.tenantCounters.get(tenantId) ?? 0;
|
|
106
|
+
const existingId = this.tenantCounterIds.get(tenantId);
|
|
107
|
+
const counter = new node_1.default.Object(COUNTER_CLASS);
|
|
108
|
+
if (existingId)
|
|
109
|
+
counter.id = existingId;
|
|
110
|
+
else {
|
|
111
|
+
counter.set("tenant", {
|
|
112
|
+
__type: "Pointer",
|
|
113
|
+
className: "OD3_Tenant",
|
|
114
|
+
objectId: tenantId,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
counter.set("value", value);
|
|
118
|
+
await counter.save(null, acl_js_1.MIGRATION_SAVE);
|
|
119
|
+
if (!existingId && counter.id)
|
|
120
|
+
this.tenantCounterIds.set(tenantId, counter.id);
|
|
121
|
+
tenants += 1;
|
|
122
|
+
}
|
|
123
|
+
return { projects, tenants };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.TicketNumberer = TicketNumberer;
|
|
127
|
+
// ── ACLs ───────────────────────────────────────────────────────────────────
|
|
128
|
+
/** Role id → role name, needed because Parse ACLs grant by name, not by id. */
|
|
129
|
+
async function loadRoleNames(config) {
|
|
130
|
+
const names = new Map();
|
|
131
|
+
for await (const page of (0, pass_js_1.pages)(() => new node_1.default.Query(node_1.default.Role), config.batchSize)) {
|
|
132
|
+
for (const role of page) {
|
|
133
|
+
const name = role.getName();
|
|
134
|
+
if (role.id && name)
|
|
135
|
+
names.set(role.id, name);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return names;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The ACL a v2 ticket should carry.
|
|
142
|
+
*
|
|
143
|
+
* `migrationAcl` covers what `defaultAclHandler` would have granted (od-admin and
|
|
144
|
+
* the tenant roles); this adds what `setTicketACL` would have granted on top —
|
|
145
|
+
* creator and assignees, read and write.
|
|
146
|
+
*
|
|
147
|
+
* Note the hook cannot actually do the assignee half today: it reads the arrays via
|
|
148
|
+
* a helper that takes `.id` off each entry, but the entity adapter stores
|
|
149
|
+
* array-of-ref fields as plain id **strings**, so it always resolves to nothing.
|
|
150
|
+
* Here we use the ids directly, so migrated tickets end up with the grants the hook
|
|
151
|
+
* was intended to give. That is a deliberate divergence, not an oversight.
|
|
152
|
+
*/
|
|
153
|
+
function ticketAcl(options) {
|
|
154
|
+
const acl = (0, acl_js_1.migrationAcl)({
|
|
155
|
+
tenantId: options.tenantId,
|
|
156
|
+
userId: options.creatorId,
|
|
157
|
+
allowTenantUserWrite: true,
|
|
158
|
+
});
|
|
159
|
+
acl.setPublicReadAccess(false);
|
|
160
|
+
acl.setPublicWriteAccess(false);
|
|
161
|
+
for (const userId of options.assignedUserIds) {
|
|
162
|
+
acl.setReadAccess(userId, true);
|
|
163
|
+
acl.setWriteAccess(userId, true);
|
|
164
|
+
}
|
|
165
|
+
for (const roleId of options.assignedRoleIds) {
|
|
166
|
+
const name = options.roleNames.get(roleId);
|
|
167
|
+
if (!name)
|
|
168
|
+
continue; // stale role reference — nothing to grant
|
|
169
|
+
acl.setRoleReadAccess(name, true);
|
|
170
|
+
acl.setRoleWriteAccess(name, true);
|
|
171
|
+
}
|
|
172
|
+
return acl;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The ACLs of the already-migrated v2 tickets, keyed by ticket id.
|
|
176
|
+
*
|
|
177
|
+
* Pass 12 needs these twice: the reconstructed changelog entry must carry the ACL
|
|
178
|
+
* of the ticket it describes (normally `beforeSave(OD3_Changelog)` mirrors it from
|
|
179
|
+
* the snapshot, but that trigger is suppressed too), and the snapshot itself should
|
|
180
|
+
* show the v2 ACL rather than the v1 one.
|
|
181
|
+
*/
|
|
182
|
+
async function loadTicketAcls(config) {
|
|
183
|
+
const acls = new Map();
|
|
184
|
+
for await (const page of (0, pass_js_1.pages)(() => new node_1.default.Query("OD3_Service_Ticket"), config.batchSize)) {
|
|
185
|
+
for (const ticket of page) {
|
|
186
|
+
const acl = ticket.getACL();
|
|
187
|
+
if (ticket.id && acl)
|
|
188
|
+
acls.set(ticket.id, acl);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return acls;
|
|
192
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Pass } from "../lib/pass.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pass 01 — `OD3_Maintenance_Kanban_State` → `OD3_Service_Status`.
|
|
4
|
+
*
|
|
5
|
+
* Runs first because everything else needs a Status to point at: `Ticket.status`
|
|
6
|
+
* is required, so pass 08 needs a fallback for tickets that never had a kanban
|
|
7
|
+
* row, and pass 04 sets `Project.defaultStatus`.
|
|
8
|
+
*
|
|
9
|
+
* Small by design — 5–10 rows per tenant — which makes it the right pass to
|
|
10
|
+
* establish the shape the others copy, and cheap to eyeball after a run.
|
|
11
|
+
*/
|
|
12
|
+
export declare const pass01: Pass;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.pass01 = void 0;
|
|
7
|
+
const node_1 = __importDefault(require("parse/node"));
|
|
8
|
+
const index_js_1 = require("../../../types/index.js");
|
|
9
|
+
const acl_js_1 = require("../lib/acl.js");
|
|
10
|
+
const map_js_1 = require("../lib/map.js");
|
|
11
|
+
const pass_js_1 = require("../lib/pass.js");
|
|
12
|
+
function categoryOf(state) {
|
|
13
|
+
if (state.get("isInbox"))
|
|
14
|
+
return "inbox";
|
|
15
|
+
if (state.get("isFinished"))
|
|
16
|
+
return "done";
|
|
17
|
+
return "active";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Pass 01 — `OD3_Maintenance_Kanban_State` → `OD3_Service_Status`.
|
|
21
|
+
*
|
|
22
|
+
* Runs first because everything else needs a Status to point at: `Ticket.status`
|
|
23
|
+
* is required, so pass 08 needs a fallback for tickets that never had a kanban
|
|
24
|
+
* row, and pass 04 sets `Project.defaultStatus`.
|
|
25
|
+
*
|
|
26
|
+
* Small by design — 5–10 rows per tenant — which makes it the right pass to
|
|
27
|
+
* establish the shape the others copy, and cheap to eyeball after a run.
|
|
28
|
+
*/
|
|
29
|
+
exports.pass01 = {
|
|
30
|
+
id: "01",
|
|
31
|
+
title: "statuses",
|
|
32
|
+
needs: [],
|
|
33
|
+
async run({ config, map, report, entry }) {
|
|
34
|
+
const states = await (0, pass_js_1.all)(() => (0, pass_js_1.scopeToTenant)(new node_1.default.Query(index_js_1.Maintenance_Kanban_State), config.tenantId), config.batchSize);
|
|
35
|
+
if (states.length === 0) {
|
|
36
|
+
report.warn(entry, "no Maintenance_Kanban_State rows found — pass 08 will have no status to fall back to");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let inboxSeen = 0;
|
|
40
|
+
for (const state of states) {
|
|
41
|
+
// Parse types `id` as optional; a row returned by a query always has one,
|
|
42
|
+
// so an absent id means something is wrong rather than something to skip
|
|
43
|
+
// silently.
|
|
44
|
+
const sourceId = state.id;
|
|
45
|
+
if (!sourceId) {
|
|
46
|
+
report.error(entry, "a Kanban_State row came back without an objectId");
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const category = categoryOf(state);
|
|
50
|
+
if (category === "inbox")
|
|
51
|
+
inboxSeen += 1;
|
|
52
|
+
const existingId = map.get(index_js_1.Maintenance_Kanban_State.className, sourceId);
|
|
53
|
+
const target = existingId
|
|
54
|
+
? new index_js_1.Service_Status({ objectId: existingId })
|
|
55
|
+
: new index_js_1.Service_Status();
|
|
56
|
+
const tenantId = (0, pass_js_1.refId)(state.get("tenant"));
|
|
57
|
+
target.set("label", state.get("label"));
|
|
58
|
+
target.set("category", category);
|
|
59
|
+
target.set("order", state.get("order"));
|
|
60
|
+
// v1 hides anything that is not exactly `enabled === true` — every v1 query
|
|
61
|
+
// filters `.equalTo("enabled", true)`. So `undefined` means hidden there, and
|
|
62
|
+
// `!== true` (not `=== false`) is what reproduces that. The schema defaults the
|
|
63
|
+
// column to true, so this only differs for rows predating that default — but
|
|
64
|
+
// getting it wrong would resurrect records v1 deliberately hid.
|
|
65
|
+
target.set("deleted", state.get("enabled") !== true);
|
|
66
|
+
if (state.get("color"))
|
|
67
|
+
target.set("color", state.get("color"));
|
|
68
|
+
if (tenantId) {
|
|
69
|
+
target.set("tenant", {
|
|
70
|
+
__type: "Pointer",
|
|
71
|
+
className: "OD3_Tenant",
|
|
72
|
+
objectId: tenantId,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// `description` has no column on Service_Status and is dropped (A1); say so
|
|
76
|
+
// rather than losing it silently.
|
|
77
|
+
if (state.get("description")) {
|
|
78
|
+
report.warn(entry, `Kanban_State ${sourceId} ("${state.get("label")}") drops description: ` +
|
|
79
|
+
`"${state.get("description")}"`);
|
|
80
|
+
}
|
|
81
|
+
if (!existingId) {
|
|
82
|
+
// Master data, not user-owned: no userId, and tenant users may write it —
|
|
83
|
+
// matching how the entity admin creates these today.
|
|
84
|
+
target.setACL((0, acl_js_1.migrationAcl)({ tenantId, allowTenantUserWrite: true }));
|
|
85
|
+
}
|
|
86
|
+
if (config.dryRun) {
|
|
87
|
+
existingId ? entry.updated++ : entry.created++;
|
|
88
|
+
// Pass 08 resolves each ticket's status through this map; without a
|
|
89
|
+
// placeholder every ticket in a dry run would warn "no Status".
|
|
90
|
+
if (!existingId) {
|
|
91
|
+
await map.record(index_js_1.Maintenance_Kanban_State.className, sourceId, index_js_1.Service_Status.className, map_js_1.MigrationMap.dryId(sourceId));
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const newId = await (0, acl_js_1.saveMigrated)(config, target, {
|
|
97
|
+
className: index_js_1.Service_Status.className,
|
|
98
|
+
isNew: !existingId,
|
|
99
|
+
createdAt: state.createdAt,
|
|
100
|
+
updatedAt: state.updatedAt,
|
|
101
|
+
});
|
|
102
|
+
if (existingId) {
|
|
103
|
+
entry.updated++;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
entry.created++;
|
|
107
|
+
await map.record(index_js_1.Maintenance_Kanban_State.className, sourceId, index_js_1.Service_Status.className, newId);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
report.error(entry, `Kanban_State ${sourceId} failed: ${error.message}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (inboxSeen === 0) {
|
|
115
|
+
report.warn(entry, "no state had isInbox — pass 08 has no inbox Status to use as the " +
|
|
116
|
+
"required-status fallback. Mark one manually before running 08.");
|
|
117
|
+
}
|
|
118
|
+
else if (inboxSeen > 1) {
|
|
119
|
+
report.warn(entry, `${inboxSeen} states had isInbox — pass 08 will pick the oldest as the fallback`);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Pass } from "../lib/pass.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pass 02 — `OD3_Maintenance_Issuecategory` → `OD3_Service_Tag`.
|
|
4
|
+
*
|
|
5
|
+
* v2 replaced issue categories with the generic tag tree, scoped per object type
|
|
6
|
+
* via `Tag.type`. Ticket categories therefore become tags typed to
|
|
7
|
+
* `OD3_Service_Ticket`.
|
|
8
|
+
*
|
|
9
|
+
* Dropped, per MIGRATION_PLAN A2 and decisions D1: `icon` and `issuecode` (no
|
|
10
|
+
* column on Tag), `catchall` (becomes "no tag"), and the `source` relation (tags
|
|
11
|
+
* are not source-scoped in v2). Each drop is reported.
|
|
12
|
+
*/
|
|
13
|
+
export declare const pass02: Pass;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.pass02 = void 0;
|
|
7
|
+
const node_1 = __importDefault(require("parse/node"));
|
|
8
|
+
const index_js_1 = require("../../../types/index.js");
|
|
9
|
+
const acl_js_1 = require("../lib/acl.js");
|
|
10
|
+
const map_js_1 = require("../lib/map.js");
|
|
11
|
+
const ptr_js_1 = require("../lib/ptr.js");
|
|
12
|
+
const pass_js_1 = require("../lib/pass.js");
|
|
13
|
+
/**
|
|
14
|
+
* Order categories so a parent is always written before its children.
|
|
15
|
+
*
|
|
16
|
+
* Two reasons this matters. `Tag.parent` needs the parent's *new* id, which only
|
|
17
|
+
* exists once the parent is written; and `afterSave(Service_Tag)` reconciles stored
|
|
18
|
+
* ancestor closures when a tag is re-parented — inserting depth-first keeps every
|
|
19
|
+
* save a create rather than a re-parent.
|
|
20
|
+
*
|
|
21
|
+
* Cycles (a category that is its own ancestor) would otherwise loop forever, so
|
|
22
|
+
* anything unreachable after a full sweep is returned separately for reporting
|
|
23
|
+
* rather than silently dropped.
|
|
24
|
+
*/
|
|
25
|
+
function topoSort(rows) {
|
|
26
|
+
const byId = new Map();
|
|
27
|
+
for (const r of rows)
|
|
28
|
+
if (r.id)
|
|
29
|
+
byId.set(r.id, r);
|
|
30
|
+
const ordered = [];
|
|
31
|
+
const placed = new Set();
|
|
32
|
+
let progressed = true;
|
|
33
|
+
while (progressed) {
|
|
34
|
+
progressed = false;
|
|
35
|
+
for (const row of rows) {
|
|
36
|
+
if (!row.id || placed.has(row.id))
|
|
37
|
+
continue;
|
|
38
|
+
const parentId = (0, pass_js_1.refId)(row.get("parent"));
|
|
39
|
+
// Placeable when it has no parent, or a parent outside this set (already
|
|
40
|
+
// gone), or a parent that is already placed.
|
|
41
|
+
if (!parentId || !byId.has(parentId) || placed.has(parentId)) {
|
|
42
|
+
ordered.push(row);
|
|
43
|
+
placed.add(row.id);
|
|
44
|
+
progressed = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
ordered,
|
|
50
|
+
unreachable: rows.filter((r) => r.id && !placed.has(r.id)),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Pass 02 — `OD3_Maintenance_Issuecategory` → `OD3_Service_Tag`.
|
|
55
|
+
*
|
|
56
|
+
* v2 replaced issue categories with the generic tag tree, scoped per object type
|
|
57
|
+
* via `Tag.type`. Ticket categories therefore become tags typed to
|
|
58
|
+
* `OD3_Service_Ticket`.
|
|
59
|
+
*
|
|
60
|
+
* Dropped, per MIGRATION_PLAN A2 and decisions D1: `icon` and `issuecode` (no
|
|
61
|
+
* column on Tag), `catchall` (becomes "no tag"), and the `source` relation (tags
|
|
62
|
+
* are not source-scoped in v2). Each drop is reported.
|
|
63
|
+
*/
|
|
64
|
+
exports.pass02 = {
|
|
65
|
+
id: "02",
|
|
66
|
+
title: "tags",
|
|
67
|
+
needs: [],
|
|
68
|
+
async run({ config, map, report, entry }) {
|
|
69
|
+
const categories = await (0, pass_js_1.all)(() => (0, pass_js_1.scopeToTenant)(new node_1.default.Query(index_js_1.Maintenance_Issuecategory), config.tenantId), config.batchSize);
|
|
70
|
+
if (categories.length === 0)
|
|
71
|
+
return;
|
|
72
|
+
const { ordered, unreachable } = topoSort(categories);
|
|
73
|
+
for (const row of unreachable) {
|
|
74
|
+
report.error(entry, `Issuecategory ${row.id} is in a parent cycle and was not migrated`);
|
|
75
|
+
}
|
|
76
|
+
let droppedCodes = 0;
|
|
77
|
+
for (const category of ordered) {
|
|
78
|
+
const sourceId = category.id;
|
|
79
|
+
if (!sourceId)
|
|
80
|
+
continue;
|
|
81
|
+
const existingId = map.get(index_js_1.Maintenance_Issuecategory.className, sourceId);
|
|
82
|
+
const target = existingId
|
|
83
|
+
? new index_js_1.Service_Tag({ objectId: existingId })
|
|
84
|
+
: new index_js_1.Service_Tag();
|
|
85
|
+
const tenantId = (0, pass_js_1.refId)(category.get("tenant"));
|
|
86
|
+
const parentSourceId = (0, pass_js_1.refId)(category.get("parent"));
|
|
87
|
+
const parentTargetId = parentSourceId
|
|
88
|
+
? map.get(index_js_1.Maintenance_Issuecategory.className, parentSourceId)
|
|
89
|
+
: undefined;
|
|
90
|
+
if (parentSourceId && !parentTargetId) {
|
|
91
|
+
// topoSort guarantees the parent came first, so this means the parent
|
|
92
|
+
// itself failed to save.
|
|
93
|
+
report.error(entry, `Issuecategory ${sourceId} ("${category.get("name")}") lost its parent ` +
|
|
94
|
+
`${parentSourceId} — parent was not migrated`);
|
|
95
|
+
}
|
|
96
|
+
target.set("label", category.get("name"));
|
|
97
|
+
target.set("type", index_js_1.Service_Ticket.className);
|
|
98
|
+
// v1 hides anything that is not exactly `enabled === true` — every v1 query
|
|
99
|
+
// filters `.equalTo("enabled", true)`. So `undefined` means hidden there, and
|
|
100
|
+
// `!== true` (not `=== false`) is what reproduces that. The schema defaults the
|
|
101
|
+
// column to true, so this only differs for rows predating that default — but
|
|
102
|
+
// getting it wrong would resurrect records v1 deliberately hid.
|
|
103
|
+
target.set("deleted", category.get("enabled") !== true);
|
|
104
|
+
const parentPtr = (0, ptr_js_1.maybePtr)(index_js_1.Service_Tag.className, parentTargetId);
|
|
105
|
+
if (parentPtr)
|
|
106
|
+
target.set("parent", parentPtr);
|
|
107
|
+
if (tenantId)
|
|
108
|
+
target.set("tenant", (0, ptr_js_1.tenantPtr)(tenantId));
|
|
109
|
+
if (category.get("issuecode"))
|
|
110
|
+
droppedCodes += 1;
|
|
111
|
+
if (category.get("icon")) {
|
|
112
|
+
report.warn(entry, `Issuecategory ${sourceId} drops icon "${category.get("icon")}"`);
|
|
113
|
+
}
|
|
114
|
+
if (!existingId) {
|
|
115
|
+
target.setACL((0, acl_js_1.migrationAcl)({ tenantId, allowTenantUserWrite: true }));
|
|
116
|
+
}
|
|
117
|
+
if (config.dryRun) {
|
|
118
|
+
existingId ? entry.updated++ : entry.created++;
|
|
119
|
+
// Dry runs still need the mapping in memory so children resolve a parent.
|
|
120
|
+
if (!existingId) {
|
|
121
|
+
await map.record(index_js_1.Maintenance_Issuecategory.className, sourceId, index_js_1.Service_Tag.className, map_js_1.MigrationMap.dryId(sourceId));
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const newId = await (0, acl_js_1.saveMigrated)(config, target, {
|
|
127
|
+
className: index_js_1.Service_Tag.className,
|
|
128
|
+
isNew: !existingId,
|
|
129
|
+
createdAt: category.createdAt,
|
|
130
|
+
updatedAt: category.updatedAt,
|
|
131
|
+
});
|
|
132
|
+
if (existingId) {
|
|
133
|
+
entry.updated++;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
entry.created++;
|
|
137
|
+
await map.record(index_js_1.Maintenance_Issuecategory.className, sourceId, index_js_1.Service_Tag.className, newId);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
report.error(entry, `Issuecategory ${sourceId} failed: ${error.message}`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (droppedCodes > 0) {
|
|
146
|
+
report.info(entry, `${droppedCodes} categor(ies) had an issuecode, which has no column on Tag ` +
|
|
147
|
+
`and was dropped (D1)`);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Pass } from "../lib/pass.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pass 03 — `OD3_Source` → `OD3_Service_Equipment`.
|
|
4
|
+
*
|
|
5
|
+
* The one genuine remodelling in the migration. v1 attaches tickets to a data
|
|
6
|
+
* source; v2 attaches them to Equipment, which is only loosely coupled back to
|
|
7
|
+
* sources through `Equipment.sourceTags`.
|
|
8
|
+
*
|
|
9
|
+
* `source.tag` is not unique — several sources can carry the same tag — so
|
|
10
|
+
* Equipment→Source is deliberately not a bijection. Storing the tag string rather
|
|
11
|
+
* than a source id is the documented intent (see the openservice-core equipment
|
|
12
|
+
* README).
|
|
13
|
+
*/
|
|
14
|
+
export declare const pass03: Pass;
|