@datarails-ai/dr-cli 1.6.96 → 1.6.97
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/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "@datarails-ai/dr-cli",
|
|
11
|
-
version: "1.6.
|
|
11
|
+
version: "1.6.97",
|
|
12
12
|
description: "Datarails CLI",
|
|
13
13
|
type: "module",
|
|
14
14
|
engines: {
|
|
@@ -67,7 +67,7 @@ var package_default = {
|
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
// src/buildInfo.ts
|
|
70
|
-
var BUILD_TIME = "2026-10-
|
|
70
|
+
var BUILD_TIME = "2026-10-05T07:44:13.912Z";
|
|
71
71
|
function formatBuildTime(iso) {
|
|
72
72
|
if (iso === "") return "";
|
|
73
73
|
const d = new Date(iso);
|
|
@@ -3624,6 +3624,10 @@ var PROD_GATE_TIER_B = /* @__PURE__ */ new Set([
|
|
|
3624
3624
|
// tier so a CSM or agent can run a MEC implementation's bulk assignment on
|
|
3625
3625
|
// prod (DR-54064).
|
|
3626
3626
|
"recon-member-batch|set",
|
|
3627
|
+
// clear-assignee --file: N `recon-member|clear` writes under one token, same
|
|
3628
|
+
// reasoning — each is undone by set-assignee, already-empty slots are
|
|
3629
|
+
// skipped, and the plan shows the sign-offs it resets (DR-54260).
|
|
3630
|
+
"recon-member-batch|clear",
|
|
3627
3631
|
"recon-member|clear",
|
|
3628
3632
|
"recon-member|set-due-date",
|
|
3629
3633
|
"recon-member|sign",
|
|
@@ -42254,31 +42258,51 @@ function validateRow(row, idx) {
|
|
|
42254
42258
|
|
|
42255
42259
|
// src/commands/_reconciliationAssigneeCsv.ts
|
|
42256
42260
|
import { promises as fs17 } from "fs";
|
|
42257
|
-
var
|
|
42258
|
-
var
|
|
42259
|
-
var
|
|
42261
|
+
var ASSIGNEE_REQUIRED = ["account_id", "type", "user_id"];
|
|
42262
|
+
var ASSIGNEE_OPTIONAL = ["user_name", "user_email"];
|
|
42263
|
+
var CLEAR_REQUIRED = ["account_id", "type"];
|
|
42260
42264
|
async function readAssigneeCsv(path11) {
|
|
42261
|
-
|
|
42265
|
+
return parseAssigneeCsvText(await readFileArg(path11));
|
|
42266
|
+
}
|
|
42267
|
+
async function readClearAssigneeCsv(path11) {
|
|
42268
|
+
return parseClearAssigneeCsvText(await readFileArg(path11));
|
|
42269
|
+
}
|
|
42270
|
+
async function readFileArg(path11) {
|
|
42262
42271
|
try {
|
|
42263
|
-
|
|
42272
|
+
return await fs17.readFile(path11, "utf8");
|
|
42264
42273
|
} catch (err) {
|
|
42265
42274
|
const m = err instanceof Error ? err.message : String(err);
|
|
42266
42275
|
throw new Error(`read --file: ${m}`);
|
|
42267
42276
|
}
|
|
42268
|
-
return parseAssigneeCsvText(raw);
|
|
42269
42277
|
}
|
|
42270
42278
|
function parseAssigneeCsvText(text2) {
|
|
42279
|
+
return parseSlotRows(text2, ASSIGNEE_REQUIRED, ASSIGNEE_OPTIONAL, (slot, cell) => {
|
|
42280
|
+
const rawUserId = cell("user_id");
|
|
42281
|
+
const userId = Number(rawUserId);
|
|
42282
|
+
if (!/^\d+$/.test(rawUserId) || !Number.isSafeInteger(userId) || userId <= 0) {
|
|
42283
|
+
throw new Error(
|
|
42284
|
+
`--file: line ${slot.lineNo}: invalid user_id "${rawUserId}" (expected positive integer)`
|
|
42285
|
+
);
|
|
42286
|
+
}
|
|
42287
|
+
return { ...slot, userId, userName: cell("user_name"), userEmail: cell("user_email") };
|
|
42288
|
+
});
|
|
42289
|
+
}
|
|
42290
|
+
function parseClearAssigneeCsvText(text2) {
|
|
42291
|
+
return parseSlotRows(text2, CLEAR_REQUIRED, [], (slot) => slot);
|
|
42292
|
+
}
|
|
42293
|
+
function parseSlotRows(text2, required2, optional, extra) {
|
|
42271
42294
|
const withoutBom = text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
|
|
42272
42295
|
const rows = parseCsvRows(withoutBom);
|
|
42273
42296
|
if (rows.length === 0) {
|
|
42274
42297
|
throw new Error("--file: CSV is empty");
|
|
42275
42298
|
}
|
|
42299
|
+
const known = /* @__PURE__ */ new Set([...required2, ...optional]);
|
|
42276
42300
|
const header = rows[0].map((h) => h.trim());
|
|
42277
42301
|
const col = /* @__PURE__ */ new Map();
|
|
42278
42302
|
header.forEach((h, i) => {
|
|
42279
|
-
if (!
|
|
42303
|
+
if (!known.has(h)) {
|
|
42280
42304
|
throw new Error(
|
|
42281
|
-
`--file: unknown CSV column "${h}" \u2014 expected ${[...
|
|
42305
|
+
`--file: unknown CSV column "${h}" \u2014 expected ${[...required2, ...optional].join(",")}`
|
|
42282
42306
|
);
|
|
42283
42307
|
}
|
|
42284
42308
|
if (col.has(h)) {
|
|
@@ -42286,7 +42310,7 @@ function parseAssigneeCsvText(text2) {
|
|
|
42286
42310
|
}
|
|
42287
42311
|
col.set(h, i);
|
|
42288
42312
|
});
|
|
42289
|
-
for (const c of
|
|
42313
|
+
for (const c of required2) {
|
|
42290
42314
|
if (!col.has(c)) {
|
|
42291
42315
|
throw new Error(`--file: CSV header is missing required column "${c}"`);
|
|
42292
42316
|
}
|
|
@@ -42295,30 +42319,24 @@ function parseAssigneeCsvText(text2) {
|
|
|
42295
42319
|
if (dataRows.length === 0) {
|
|
42296
42320
|
throw new Error("--file: CSV has no data rows (header only)");
|
|
42297
42321
|
}
|
|
42298
|
-
const cell = (cells, name) => {
|
|
42299
|
-
const i = col.get(name);
|
|
42300
|
-
return i === void 0 ? "" : (cells[i] ?? "").trim();
|
|
42301
|
-
};
|
|
42302
42322
|
const seenSlots = /* @__PURE__ */ new Map();
|
|
42303
42323
|
const out = [];
|
|
42304
42324
|
for (const { cells, lineNo } of dataRows) {
|
|
42305
|
-
const
|
|
42325
|
+
const cell = (name) => {
|
|
42326
|
+
const i = col.get(name);
|
|
42327
|
+
return i === void 0 ? "" : (cells[i] ?? "").trim();
|
|
42328
|
+
};
|
|
42329
|
+
const accountId = cell("account_id");
|
|
42306
42330
|
if (accountId === "") {
|
|
42307
42331
|
throw new Error(`--file: line ${lineNo}: "account_id" is required`);
|
|
42308
42332
|
}
|
|
42309
|
-
const rawType = cell(
|
|
42333
|
+
const rawType = cell("type");
|
|
42310
42334
|
if (rawType !== "Preparer" && rawType !== "Reviewer") {
|
|
42311
42335
|
throw new Error(
|
|
42312
42336
|
`--file: line ${lineNo}: invalid type "${rawType}" (expected "Preparer" or "Reviewer")`
|
|
42313
42337
|
);
|
|
42314
42338
|
}
|
|
42315
|
-
const
|
|
42316
|
-
const userId = Number(rawUserId);
|
|
42317
|
-
if (!/^\d+$/.test(rawUserId) || !Number.isSafeInteger(userId) || userId <= 0) {
|
|
42318
|
-
throw new Error(
|
|
42319
|
-
`--file: line ${lineNo}: invalid user_id "${rawUserId}" (expected positive integer)`
|
|
42320
|
-
);
|
|
42321
|
-
}
|
|
42339
|
+
const row = extra({ lineNo, accountId, type: rawType }, cell);
|
|
42322
42340
|
const slot = `${accountId}:${rawType}`;
|
|
42323
42341
|
const prior = seenSlots.get(slot);
|
|
42324
42342
|
if (prior !== void 0) {
|
|
@@ -42327,14 +42345,7 @@ function parseAssigneeCsvText(text2) {
|
|
|
42327
42345
|
);
|
|
42328
42346
|
}
|
|
42329
42347
|
seenSlots.set(slot, lineNo);
|
|
42330
|
-
out.push(
|
|
42331
|
-
lineNo,
|
|
42332
|
-
accountId,
|
|
42333
|
-
type: rawType,
|
|
42334
|
-
userId,
|
|
42335
|
-
userName: cell(cells, "user_name"),
|
|
42336
|
-
userEmail: cell(cells, "user_email")
|
|
42337
|
-
});
|
|
42348
|
+
out.push(row);
|
|
42338
42349
|
}
|
|
42339
42350
|
return out;
|
|
42340
42351
|
}
|
|
@@ -44025,52 +44036,186 @@ verify the date matches the accounts' reporting period (try \`dr reconciliation
|
|
|
44025
44036
|
account_id: id,
|
|
44026
44037
|
members: applyAssignments(byId.get(id), changesByAccount.get(id))
|
|
44027
44038
|
}));
|
|
44028
|
-
|
|
44029
|
-
|
|
44030
|
-
|
|
44031
|
-
|
|
44032
|
-
|
|
44033
|
-
|
|
44034
|
-
|
|
44035
|
-
|
|
44036
|
-
|
|
44037
|
-
|
|
44038
|
-
|
|
44039
|
-
|
|
44040
|
-
|
|
44041
|
-
|
|
44042
|
-
|
|
44043
|
-
|
|
44044
|
-
|
|
44045
|
-
|
|
44046
|
-
|
|
44047
|
-
|
|
44039
|
+
const applied = await writeMemberChunks(cfg, byId, items, chunkSize);
|
|
44040
|
+
if (cfg.asJson) {
|
|
44041
|
+
const changed = accountIds.flatMap((id) => changesByAccount.get(id).map(toJsonRow));
|
|
44042
|
+
writeJSONIndented(cfg.out, { changed, unchanged: unchanged.map(toJsonRow) });
|
|
44043
|
+
return;
|
|
44044
|
+
}
|
|
44045
|
+
cfg.out.write(
|
|
44046
|
+
`Set ${changeCount} role${plural(changeCount)} on ${applied} account${plural(applied)} (${unchanged.length} unchanged).
|
|
44047
|
+
`
|
|
44048
|
+
);
|
|
44049
|
+
}
|
|
44050
|
+
});
|
|
44051
|
+
}
|
|
44052
|
+
function clearBatchActionId(date, rows) {
|
|
44053
|
+
const canonical = rows.map((r) => [r.accountId, r.type]);
|
|
44054
|
+
return createHash8("sha256").update(JSON.stringify(["clear", date, canonical])).digest("hex").slice(0, 12);
|
|
44055
|
+
}
|
|
44056
|
+
async function runReconciliationAccountsClearAssigneeBatch(cfg) {
|
|
44057
|
+
const action = {
|
|
44058
|
+
resource: "recon-member-batch",
|
|
44059
|
+
id: clearBatchActionId(cfg.date, cfg.rows),
|
|
44060
|
+
verb: "clear"
|
|
44061
|
+
};
|
|
44062
|
+
await preflightConfirmToken(action, cfg.token);
|
|
44063
|
+
extendForBatch(cfg.extendTimeout, 1);
|
|
44064
|
+
const accounts = await getAccounts(
|
|
44065
|
+
cfg.client,
|
|
44066
|
+
{ date: cfg.date },
|
|
44067
|
+
{ signal: cfg.signal }
|
|
44068
|
+
);
|
|
44069
|
+
const byId = new Map(accounts.map((a) => [a.account_id, a]));
|
|
44070
|
+
const problems = [];
|
|
44071
|
+
for (const row of cfg.rows) {
|
|
44072
|
+
const account = byId.get(row.accountId);
|
|
44073
|
+
if (account === void 0) {
|
|
44074
|
+
problems.push(`line ${row.lineNo}: account "${row.accountId}" not found at --date`);
|
|
44075
|
+
} else if (account.members.length > 0 && !account.members.some((m) => m.type === row.type)) {
|
|
44076
|
+
problems.push(
|
|
44077
|
+
`line ${row.lineNo}: account "${row.accountId}" has a partial member set (${account.members.map((m) => m.type).join(", ")}) and is missing ${row.type}`
|
|
44078
|
+
);
|
|
44079
|
+
}
|
|
44080
|
+
}
|
|
44081
|
+
if (problems.length > 0) {
|
|
44082
|
+
const shown = problems.slice(0, 10);
|
|
44083
|
+
const more = problems.length > 10 ? [`\u2026 (+${problems.length - 10} more)`] : [];
|
|
44084
|
+
throw new Error(
|
|
44085
|
+
`--file: ${problems.length} row${plural(problems.length)} cannot be applied; nothing was written:
|
|
44086
|
+
` + [...shown, ...more].join("\n ") + `
|
|
44087
|
+
verify the date matches the accounts' reporting period (try \`dr reconciliation accounts list --date \u2026\`)`
|
|
44088
|
+
);
|
|
44089
|
+
}
|
|
44090
|
+
const clearsByAccount = /* @__PURE__ */ new Map();
|
|
44091
|
+
const unchanged = [];
|
|
44092
|
+
for (const row of cfg.rows) {
|
|
44093
|
+
const current = byId.get(row.accountId).members.find((m) => m.type === row.type);
|
|
44094
|
+
if (current === void 0 || current.user_id === 0) {
|
|
44095
|
+
unchanged.push(row);
|
|
44096
|
+
continue;
|
|
44097
|
+
}
|
|
44098
|
+
const list28 = clearsByAccount.get(row.accountId) ?? [];
|
|
44099
|
+
list28.push({ row, current });
|
|
44100
|
+
clearsByAccount.set(row.accountId, list28);
|
|
44101
|
+
}
|
|
44102
|
+
const clearCount = cfg.rows.length - unchanged.length;
|
|
44103
|
+
const n = cfg.rows.length;
|
|
44104
|
+
if (clearCount === 0) {
|
|
44105
|
+
if (cfg.asJson) {
|
|
44106
|
+
writeJSONIndented(cfg.out, {
|
|
44107
|
+
changed: [],
|
|
44108
|
+
unchanged: unchanged.map((r) => ({ account_id: r.accountId, type: r.type }))
|
|
44109
|
+
});
|
|
44110
|
+
return;
|
|
44111
|
+
}
|
|
44112
|
+
cfg.out.write(
|
|
44113
|
+
`Nothing to clear: all ${n} row${plural(n)} name a slot that is already Unassigned or does not exist yet.
|
|
44114
|
+
`
|
|
44115
|
+
);
|
|
44116
|
+
return;
|
|
44117
|
+
}
|
|
44118
|
+
const chunkSize = cfg.chunkSize ?? ASSIGNEE_BATCH_CHUNK_SIZE;
|
|
44119
|
+
const accountIds = [...clearsByAccount.keys()];
|
|
44120
|
+
const requests = Math.ceil(accountIds.length / chunkSize);
|
|
44121
|
+
await twoStepConfirm({
|
|
44122
|
+
out: cfg.out,
|
|
44123
|
+
action,
|
|
44124
|
+
token: cfg.token,
|
|
44125
|
+
server: cfg.serverFlag,
|
|
44126
|
+
confirmCmd: cfg.confirmCmd,
|
|
44127
|
+
describe: async () => {
|
|
44128
|
+
let signOffsReset = 0;
|
|
44129
|
+
const preview = [];
|
|
44130
|
+
for (const id of accountIds) {
|
|
44131
|
+
const account = byId.get(id);
|
|
44132
|
+
for (const { row, current } of clearsByAccount.get(id)) {
|
|
44133
|
+
if (current.is_sign_off) signOffsReset += 1;
|
|
44134
|
+
if (preview.length < 5) {
|
|
44135
|
+
preview.push(
|
|
44136
|
+
` ${account.account_name} (${id}) ${row.type}: ${current.name} (user ${current.user_id}) \u2192 Unassigned`
|
|
44048
44137
|
);
|
|
44049
44138
|
}
|
|
44050
44139
|
}
|
|
44051
|
-
try {
|
|
44052
|
-
await updateAccountMembers(cfg.client, chunk, { signal: cfg.signal });
|
|
44053
|
-
} catch (err) {
|
|
44054
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
44055
|
-
throw new Error(
|
|
44056
|
-
`PATCH failed after ${applied} of ${items.length} account${plural(items.length)} were written (accounts ${i + 1}\u2013${i + chunk.length} may be partially applied): ${msg}
|
|
44057
|
-
Re-run the same command without --confirm-token: rows that landed plan as unchanged, so the new plan covers only the rest.`
|
|
44058
|
-
);
|
|
44059
|
-
}
|
|
44060
|
-
applied += chunk.length;
|
|
44061
44140
|
}
|
|
44141
|
+
const lines = [
|
|
44142
|
+
`rows: ${n}`,
|
|
44143
|
+
`to clear: ${clearCount} role${plural(clearCount)} on ${accountIds.length} account${plural(accountIds.length)} (${requests} PATCH request${plural(requests)})`,
|
|
44144
|
+
`unchanged: ${unchanged.length} (slot already Unassigned, or the account has no member slots \u2014 skipped)`,
|
|
44145
|
+
`sign-offs reset: ${signOffsReset}`,
|
|
44146
|
+
"changes:",
|
|
44147
|
+
...preview
|
|
44148
|
+
];
|
|
44149
|
+
if (clearCount > preview.length) lines.push(` \u2026 (+${clearCount - preview.length} more)`);
|
|
44150
|
+
return lines;
|
|
44151
|
+
},
|
|
44152
|
+
execute: async () => {
|
|
44153
|
+
const items = accountIds.map((id) => {
|
|
44154
|
+
const cleared = new Set(clearsByAccount.get(id).map((c) => c.row.type));
|
|
44155
|
+
return {
|
|
44156
|
+
account_id: id,
|
|
44157
|
+
members: byId.get(id).members.map(
|
|
44158
|
+
(m) => cleared.has(m.type) ? unassignedMember(m.type, m.due_date) : memberSummaryToWire(m)
|
|
44159
|
+
)
|
|
44160
|
+
};
|
|
44161
|
+
});
|
|
44162
|
+
const applied = await writeMemberChunks(cfg, byId, items, chunkSize);
|
|
44062
44163
|
if (cfg.asJson) {
|
|
44063
|
-
|
|
44064
|
-
|
|
44164
|
+
writeJSONIndented(cfg.out, {
|
|
44165
|
+
changed: accountIds.flatMap(
|
|
44166
|
+
(id) => clearsByAccount.get(id).map(({ row, current }) => ({
|
|
44167
|
+
account_id: row.accountId,
|
|
44168
|
+
type: row.type,
|
|
44169
|
+
previous_user_id: current.user_id
|
|
44170
|
+
}))
|
|
44171
|
+
),
|
|
44172
|
+
unchanged: unchanged.map((r) => ({ account_id: r.accountId, type: r.type }))
|
|
44173
|
+
});
|
|
44065
44174
|
return;
|
|
44066
44175
|
}
|
|
44067
44176
|
cfg.out.write(
|
|
44068
|
-
`
|
|
44177
|
+
`Cleared ${clearCount} role${plural(clearCount)} on ${applied} account${plural(applied)} (${unchanged.length} unchanged).
|
|
44069
44178
|
`
|
|
44070
44179
|
);
|
|
44071
44180
|
}
|
|
44072
44181
|
});
|
|
44073
44182
|
}
|
|
44183
|
+
async function writeMemberChunks(ctx, planned, items, chunkSize) {
|
|
44184
|
+
const requests = Math.ceil(items.length / chunkSize);
|
|
44185
|
+
extendForBatch(ctx.extendTimeout, requests * 2);
|
|
44186
|
+
let applied = 0;
|
|
44187
|
+
for (let i = 0; i < items.length; i += chunkSize) {
|
|
44188
|
+
const chunk = items.slice(i, i + chunkSize);
|
|
44189
|
+
let drift;
|
|
44190
|
+
try {
|
|
44191
|
+
drift = await chunkDrift(ctx, planned, chunk);
|
|
44192
|
+
} catch (err) {
|
|
44193
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
44194
|
+
throw new Error(
|
|
44195
|
+
`re-read failed after ${applied} of ${items.length} account${plural(items.length)} were written; nothing more was sent: ${msg}
|
|
44196
|
+
Re-run the same command without --confirm-token: rows that landed plan as unchanged, so the new plan covers only the rest.`
|
|
44197
|
+
);
|
|
44198
|
+
}
|
|
44199
|
+
if (drift.length > 0) {
|
|
44200
|
+
throw new Error(
|
|
44201
|
+
`halted after ${applied} of ${items.length} account${plural(items.length)} were written; nothing more was sent:
|
|
44202
|
+
` + drift.join("\n ") + `
|
|
44203
|
+
The tenant changed since the plan. Re-run the same command without --confirm-token: rows that landed plan as unchanged. If accounts are gone, the trial balance was rebuilt and their ids changed \u2014 wait for it to settle and rebuild the file from \`dr reconciliation accounts list\`.`
|
|
44204
|
+
);
|
|
44205
|
+
}
|
|
44206
|
+
try {
|
|
44207
|
+
await updateAccountMembers(ctx.client, chunk, { signal: ctx.signal });
|
|
44208
|
+
} catch (err) {
|
|
44209
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
44210
|
+
throw new Error(
|
|
44211
|
+
`PATCH failed after ${applied} of ${items.length} account${plural(items.length)} were written (accounts ${i + 1}\u2013${i + chunk.length} may be partially applied): ${msg}
|
|
44212
|
+
Re-run the same command without --confirm-token: rows that landed plan as unchanged, so the new plan covers only the rest.`
|
|
44213
|
+
);
|
|
44214
|
+
}
|
|
44215
|
+
applied += chunk.length;
|
|
44216
|
+
}
|
|
44217
|
+
return applied;
|
|
44218
|
+
}
|
|
44074
44219
|
async function chunkDrift(cfg, planned, chunk) {
|
|
44075
44220
|
const now = await getAccounts(cfg.client, { date: cfg.date }, { signal: cfg.signal });
|
|
44076
44221
|
const nowById = new Map(now.map((a) => [a.account_id, a]));
|
|
@@ -44579,16 +44724,69 @@ function wireReconciliation(program) {
|
|
|
44579
44724
|
);
|
|
44580
44725
|
addServerFlag(setAssigneeCmd);
|
|
44581
44726
|
addAccountFlag(setAssigneeCmd);
|
|
44582
|
-
const clearAssigneeCmd = accounts.command("clear-assignee").description(
|
|
44583
|
-
|
|
44584
|
-
|
|
44727
|
+
const clearAssigneeCmd = accounts.command("clear-assignee").summary("Clear the Preparer or Reviewer slot of one account, or many with --file").description(
|
|
44728
|
+
[
|
|
44729
|
+
"Clear the Preparer or Reviewer slot (sets user_id=0 + Unassigned name/email; preserves due_date, resets sign-off)",
|
|
44730
|
+
"",
|
|
44731
|
+
"Bulk form \u2014 one plan and one token for many slots:",
|
|
44732
|
+
" dr reconciliation accounts clear-assignee --file slots.csv --date 2025-04-30",
|
|
44733
|
+
" # CSV header: account_id,type \u2014 one row per (account, role)"
|
|
44734
|
+
].join("\n")
|
|
44735
|
+
).argument("[account_uuid]", "account uuid; omit with --file").requiredOption("--date <YYYY-MM-DD>", "Reporting date").option("--type <s>", "Preparer | Reviewer (case-sensitive); required without --file").option(
|
|
44736
|
+
"--file <path>",
|
|
44737
|
+
"CSV of slots to clear (header account_id,type); one plan + one token for the whole file"
|
|
44738
|
+
).option(
|
|
44739
|
+
"--json",
|
|
44740
|
+
"Output the cleared member record(s) as JSON instead of a confirmation",
|
|
44741
|
+
false
|
|
44742
|
+
).option("--timeout <seconds>", `With --file: ${BATCH_TIMEOUT_FLAG_HELP}`).option(
|
|
44585
44743
|
"--confirm-token <token>",
|
|
44586
44744
|
"Token from a prior plan run \u2014 required to actually clear",
|
|
44587
44745
|
""
|
|
44588
44746
|
).action(
|
|
44589
44747
|
async (accountUuid, opts) => {
|
|
44590
44748
|
const date = parseDateFlag(opts.date ?? "", "--date");
|
|
44591
|
-
|
|
44749
|
+
if (opts.file !== void 0) {
|
|
44750
|
+
const conflicting = [
|
|
44751
|
+
accountUuid !== void 0 ? "<account_uuid>" : "",
|
|
44752
|
+
opts.type !== void 0 ? "--type" : ""
|
|
44753
|
+
].filter((s) => s !== "");
|
|
44754
|
+
if (conflicting.length > 0) {
|
|
44755
|
+
throw new Error(
|
|
44756
|
+
`--file cannot be combined with ${conflicting.join(", ")} (the CSV carries them per row)`
|
|
44757
|
+
);
|
|
44758
|
+
}
|
|
44759
|
+
const rows = await readClearAssigneeCsv(opts.file);
|
|
44760
|
+
await preflightConfirmToken(
|
|
44761
|
+
{ resource: "recon-member-batch", id: clearBatchActionId(date, rows), verb: "clear" },
|
|
44762
|
+
opts.confirmToken
|
|
44763
|
+
);
|
|
44764
|
+
const serverKey2 = resolveServer(opts.server);
|
|
44765
|
+
const confirmCmd2 = `dr reconciliation accounts clear-assignee --file ${JSON.stringify(opts.file)} --date ${opts.date}` + (opts.json === true ? " --json" : "") + (opts.timeout !== void 0 ? ` --timeout ${opts.timeout}` : "") + ` --server ${serverKey2}`;
|
|
44766
|
+
const setup2 = await setupCall(opts.server, opts.account, batchSetupOptions(opts.timeout));
|
|
44767
|
+
try {
|
|
44768
|
+
await runReconciliationAccountsClearAssigneeBatch({
|
|
44769
|
+
client: setup2.client,
|
|
44770
|
+
extendTimeout: setup2.extendTimeout,
|
|
44771
|
+
date,
|
|
44772
|
+
rows,
|
|
44773
|
+
token: opts.confirmToken,
|
|
44774
|
+
confirmCmd: confirmCmd2,
|
|
44775
|
+
serverFlag: serverKey2,
|
|
44776
|
+
asJson: opts.json === true,
|
|
44777
|
+
signal: setup2.signal,
|
|
44778
|
+
out: process.stdout
|
|
44779
|
+
});
|
|
44780
|
+
} finally {
|
|
44781
|
+
setup2.cleanup();
|
|
44782
|
+
}
|
|
44783
|
+
return;
|
|
44784
|
+
}
|
|
44785
|
+
if (accountUuid === void 0) {
|
|
44786
|
+
throw new Error("missing <account_uuid> (or pass --file <csv> for many slots)");
|
|
44787
|
+
}
|
|
44788
|
+
if (opts.type === void 0) throw new Error("required option '--type <s>' not specified");
|
|
44789
|
+
const type = parseMemberTypeFlag(opts.type, "--type");
|
|
44592
44790
|
await preflightConfirmToken(
|
|
44593
44791
|
{
|
|
44594
44792
|
resource: "recon-member",
|
package/dist/scopes-map.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"cliVersion": "1.6.
|
|
3
|
+
"cliVersion": "1.6.97",
|
|
4
4
|
"_contract": "Resolve a command path through `aliases` (canonical prefix + alias segment -> canonical path) before any lookup. A path listed in `pendingCommands`, or whose first segment is a key of `pending`, is UNDECIDED, not unrestricted: fail closed or skip it, and never read a path's absence from `commands` as 'no requirement'. An entry carrying `denied` is UNAVAILABLE to any AGENT-SCOPED credential — one whose `scopes` claim carries at least one `resource:action` entry; a claim that is absent, empty, capability-only or wholly unparseable is UNRESTRICTED, and this mark does not apply to it — whatever else that entry says: it is not a ceiling and it is not always-available, its `denied.reason` says why and `denied.until` names the ticket that lifts it, and the absence of `requires` on it must NEVER be read as 'no requirement'. Only an entry in `commands` with an empty `requires` means always-available.",
|
|
5
5
|
"commands": {
|
|
6
6
|
"login": {
|
|
@@ -45,8 +45,8 @@ Account assignment / sign-off (all take the bank-account UUID via
|
|
|
45
45
|
resets. Every account is checked before anything is written, and the
|
|
46
46
|
write goes out in PATCHes of 100 accounts. If one fails, the error says
|
|
47
47
|
how many accounts landed; re-run the same file without a token and the
|
|
48
|
-
new plan covers only the rest. Before each PATCH
|
|
49
|
-
|
|
48
|
+
new plan covers only the rest. Before each PATCH, the accounts are
|
|
49
|
+
re-read; if any account in the next chunk is gone (a
|
|
50
50
|
trial-balance rebuild re-mints account ids) or its member slots changed
|
|
51
51
|
since the plan, the run halts, says how many accounts landed, and names
|
|
52
52
|
the drifted accounts. Gone accounts mean the file must be rebuilt from
|
|
@@ -55,6 +55,13 @@ Account assignment / sign-off (all take the bank-account UUID via
|
|
|
55
55
|
- `reconciliation accounts clear-assignee <account_uuid> --date <YYYY-MM-DD>
|
|
56
56
|
--type Preparer|Reviewer` — unassigns (preserves due_date, resets sign-off).
|
|
57
57
|
Two-step confirm.
|
|
58
|
+
- `reconciliation accounts clear-assignee --file <csv> --date <YYYY-MM-DD>` —
|
|
59
|
+
the bulk form. Header `account_id,type`, one row per (account, role); the
|
|
60
|
+
user columns of a set-assignee file are rejected, so a set-assignee
|
|
61
|
+
manifest cannot be passed here by mistake. One plan and one token cover the
|
|
62
|
+
whole file. Slots already Unassigned, and accounts with no member slots,
|
|
63
|
+
are skipped. Same 100-account PATCHes, per-chunk re-check, and resume
|
|
64
|
+
behaviour as `set-assignee --file`.
|
|
58
65
|
- `reconciliation accounts set-due-date <account_uuid> --date <YYYY-MM-DD>
|
|
59
66
|
--type Preparer|Reviewer (--due-date <YYYY-MM-DD> | --epoch <n> | --clear)`.
|
|
60
67
|
Two-step confirm.
|