chore(global): remove a bunch of test and temp files
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
import { PrismaMssql } from "@prisma/adapter-mssql";
|
||||
import { PrismaClient } from "./generated/prisma/client";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
if (!connectionString) {
|
||||
throw new Error("DATABASE_URL is not set.");
|
||||
}
|
||||
|
||||
const prisma = new PrismaClient({
|
||||
adapter: new PrismaMssql(connectionString),
|
||||
});
|
||||
|
||||
try {
|
||||
const rowSummary = await prisma.$queryRawUnsafe<
|
||||
Array<{ total_rows: number; distinct_configs: number }>
|
||||
>(`
|
||||
SELECT
|
||||
COUNT(*) AS total_rows,
|
||||
COUNT(DISTINCT Config_RecID) AS distinct_configs
|
||||
FROM dbo.Config_User_Defined_Field_Value;
|
||||
`);
|
||||
|
||||
const relatedRowCounts = await prisma.$queryRawUnsafe<
|
||||
Array<{
|
||||
config_rows: number;
|
||||
cs_result_detail_rows: number;
|
||||
config_custom_field_nonempty: number;
|
||||
}>
|
||||
>(`
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM dbo.Config) AS config_rows,
|
||||
(SELECT COUNT(*) FROM dbo.CS_Result_Detail) AS cs_result_detail_rows,
|
||||
(SELECT COUNT(*)
|
||||
FROM dbo.Config
|
||||
WHERE Custom_Field IS NOT NULL
|
||||
AND LEN(LTRIM(RTRIM(CONVERT(nvarchar(max), Custom_Field)))) > 0) AS config_custom_field_nonempty;
|
||||
`);
|
||||
|
||||
const topConfigs = await prisma.$queryRawUnsafe<
|
||||
Array<{ config_recid: number; field_count: number }>
|
||||
>(`
|
||||
SELECT TOP 10
|
||||
Config_RecID AS config_recid,
|
||||
COUNT(*) AS field_count
|
||||
FROM dbo.Config_User_Defined_Field_Value
|
||||
GROUP BY Config_RecID
|
||||
ORDER BY field_count DESC, config_recid ASC;
|
||||
`);
|
||||
|
||||
const customFieldSamples = await prisma.$queryRawUnsafe<
|
||||
Array<{ config_recid: number; custom_field_prefix: string }>
|
||||
>(`
|
||||
SELECT TOP 5
|
||||
Config_RecID AS config_recid,
|
||||
LEFT(CONVERT(nvarchar(max), Custom_Field), 250) AS custom_field_prefix
|
||||
FROM dbo.Config
|
||||
WHERE Custom_Field IS NOT NULL
|
||||
AND LEN(LTRIM(RTRIM(CONVERT(nvarchar(max), Custom_Field)))) > 0
|
||||
ORDER BY Config_RecID ASC;
|
||||
`);
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
rowSummary: rowSummary[0] ?? null,
|
||||
relatedRowCounts: relatedRowCounts[0] ?? null,
|
||||
topConfigs,
|
||||
customFieldSamples,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { PrismaMssql } from "@prisma/adapter-mssql";
|
||||
import { PrismaClient } from "./generated/prisma/client";
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
const outputPath =
|
||||
process.argv[2] ??
|
||||
process.env.CONFIG_OUTPUT_FILE ??
|
||||
"configurations-first-10-with-relations.json";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
if (!connectionString) {
|
||||
throw new Error("DATABASE_URL is not set.");
|
||||
}
|
||||
|
||||
const adapter = new PrismaMssql(connectionString);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
try {
|
||||
const configurations = await prisma.configuration.findMany({
|
||||
take: 10,
|
||||
orderBy: { configRecId: "asc" },
|
||||
include: {
|
||||
configStatus: true,
|
||||
configurationAudits: {
|
||||
orderBy: { lastUpdatedUtc: "desc" },
|
||||
include: {
|
||||
configurationValues: {
|
||||
orderBy: { configurationAuditValueRecId: "asc" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (configurations.length === 0) {
|
||||
console.error("No configurations found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
writeFileSync(outputPath, JSON.stringify(configurations, null, 2));
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import fetchOpportunities from "./old-src/collectors/fetchOpportunities";
|
||||
|
||||
fetchOpportunities({
|
||||
include: [
|
||||
"company",
|
||||
"activities",
|
||||
"opportunityNotes",
|
||||
"forecastItems",
|
||||
"contacts",
|
||||
],
|
||||
}).then((opportunities) => {
|
||||
const jsonData = JSON.stringify(opportunities, null, 2);
|
||||
const { writeFileSync } = require("fs");
|
||||
writeFileSync("examples/opportunity-with-relations.json", jsonData);
|
||||
console.log(
|
||||
`Exported ${opportunities.length} opportunities to examples/opportunity-with-relations.json`
|
||||
);
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import { PrismaClient } from "./generated/prisma/client";
|
||||
import { PrismaMssql } from "@prisma/adapter-mssql";
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
|
||||
if (!connectionString) {
|
||||
throw new Error("DATABASE_URL is not set.");
|
||||
}
|
||||
|
||||
const adapter = new PrismaMssql(connectionString);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
const products = await prisma.productCatalog.findMany({
|
||||
where: { inactiveFlag: false },
|
||||
include: {
|
||||
subcategory: { include: { category: true } },
|
||||
manufacturer: true,
|
||||
inventory: true,
|
||||
itemVendors: true,
|
||||
},
|
||||
take: 100,
|
||||
});
|
||||
|
||||
writeFileSync(
|
||||
"products-with-relations.json",
|
||||
JSON.stringify(products, null, 2),
|
||||
);
|
||||
console.log(`Exported ${products.length} products`);
|
||||
await prisma.$disconnect();
|
||||
@@ -1,150 +0,0 @@
|
||||
import { PrismaMssql } from "@prisma/adapter-mssql";
|
||||
import { Prisma, PrismaClient } from "./generated/prisma/client";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
if (!connectionString) {
|
||||
throw new Error("DATABASE_URL is not set.");
|
||||
}
|
||||
|
||||
const adapter = new PrismaMssql(connectionString);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
type CandidateTable = { table_name: string };
|
||||
type CandidateColumn = { table_name: string; column_name: string };
|
||||
|
||||
type DmmfField = {
|
||||
name: string;
|
||||
dbName: string | null;
|
||||
};
|
||||
|
||||
type DmmfModel = {
|
||||
name: string;
|
||||
dbName: string | null;
|
||||
fields: DmmfField[];
|
||||
};
|
||||
|
||||
const TABLE_PATTERN = /config|configur/i;
|
||||
const VALUE_COLUMN_PATTERN = /value|field|question|token/i;
|
||||
const TOP_CONFIG_LIMIT = 150;
|
||||
const CONFIG_KEY_COLUMNS = new Set([
|
||||
"Config_RecID",
|
||||
"Configuration_RecID",
|
||||
"Configuration_RecId",
|
||||
]);
|
||||
|
||||
function byName(a: string, b: string) {
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
|
||||
try {
|
||||
const models = Prisma.dmmf.datamodel.models as unknown as DmmfModel[];
|
||||
|
||||
const configModels = models
|
||||
.map((model) => ({
|
||||
model,
|
||||
tableName: model.dbName ?? model.name,
|
||||
}))
|
||||
.filter(({ tableName }) => TABLE_PATTERN.test(tableName));
|
||||
|
||||
const candidateTables: CandidateTable[] = configModels
|
||||
.map(({ tableName }) => ({ table_name: tableName }))
|
||||
.sort((a, b) => byName(a.table_name, b.table_name));
|
||||
|
||||
const candidateColumns: CandidateColumn[] = configModels
|
||||
.flatMap(({ model, tableName }) =>
|
||||
model.fields
|
||||
.map((field) => field.dbName ?? field.name)
|
||||
.filter((columnName) => VALUE_COLUMN_PATTERN.test(columnName))
|
||||
.map((columnName) => ({
|
||||
table_name: tableName,
|
||||
column_name: columnName,
|
||||
}))
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
byName(a.table_name, b.table_name) ||
|
||||
byName(a.column_name, b.column_name)
|
||||
);
|
||||
|
||||
const valueTablesWithConfigKey: CandidateTable[] = configModels
|
||||
.filter(({ model }) => {
|
||||
const columnNames = model.fields.map(
|
||||
(field) => field.dbName ?? field.name
|
||||
);
|
||||
const hasConfigKey = columnNames.some((column) =>
|
||||
CONFIG_KEY_COLUMNS.has(column)
|
||||
);
|
||||
const hasValueLikeColumn = columnNames.some((column) =>
|
||||
VALUE_COLUMN_PATTERN.test(column)
|
||||
);
|
||||
return hasConfigKey && hasValueLikeColumn;
|
||||
})
|
||||
.map(({ tableName }) => ({ table_name: tableName }))
|
||||
.sort((a, b) => byName(a.table_name, b.table_name));
|
||||
|
||||
const [
|
||||
configRows,
|
||||
auditRows,
|
||||
auditValueRows,
|
||||
nonNullCustomFields,
|
||||
groupedAuditTokens,
|
||||
topConfigs,
|
||||
] = await prisma.$transaction([
|
||||
prisma.configuration.count(),
|
||||
prisma.configurationAudit.count(),
|
||||
prisma.configurationAuditValue.count(),
|
||||
prisma.configuration.findMany({
|
||||
where: { customField: { not: null } },
|
||||
select: { customField: true },
|
||||
}),
|
||||
prisma.configurationAuditValue.groupBy({
|
||||
by: ["auditToken"],
|
||||
_count: true,
|
||||
orderBy: [{ _count: { auditToken: "desc" } }, { auditToken: "asc" }],
|
||||
take: 20,
|
||||
}),
|
||||
prisma.configuration.findMany({
|
||||
take: TOP_CONFIG_LIMIT,
|
||||
orderBy: { configRecId: "asc" },
|
||||
include: {
|
||||
configurationAudits: {
|
||||
orderBy: { configurationAuditRecId: "asc" },
|
||||
include: {
|
||||
configurationValues: {
|
||||
orderBy: { configurationAuditValueRecId: "asc" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const configCustomFieldNonempty = nonNullCustomFields.reduce((count, row) => {
|
||||
return row.customField?.trim() ? count + 1 : count;
|
||||
}, 0);
|
||||
|
||||
const rowStats = {
|
||||
config_rows: configRows,
|
||||
config_custom_field_nonempty: configCustomFieldNonempty,
|
||||
audit_rows: auditRows,
|
||||
audit_value_rows: auditValueRows,
|
||||
};
|
||||
|
||||
const topAuditTokens = groupedAuditTokens.map(({ auditToken, _count }) => ({
|
||||
audit_token: auditToken,
|
||||
row_count: _count,
|
||||
}));
|
||||
|
||||
const output = {
|
||||
candidateTables,
|
||||
candidateColumns,
|
||||
valueTablesWithConfigKey,
|
||||
rowStats,
|
||||
topAuditTokens,
|
||||
topConfigs,
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(output, null, 2));
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
import { PrismaMssql } from "@prisma/adapter-mssql";
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { PrismaClient as CwPrismaClient } from "./generated/prisma/client";
|
||||
import { PrismaClient as ApiPrismaClient } from "../api/generated/prisma/client";
|
||||
|
||||
type EnvMap = Record<string, string>;
|
||||
|
||||
type Summary = {
|
||||
cwTotal: number;
|
||||
apiTotal: number;
|
||||
eligibleForSync: number;
|
||||
missingSrServiceRecId: number;
|
||||
missingMemberRecId: number;
|
||||
missingParentTicketInApi: number;
|
||||
missingAuthorMappingInApi: number;
|
||||
eligibleButMissingInApi: number;
|
||||
sampleMissingParentTicketIds: number[];
|
||||
sampleMissingAuthorMemberRecIds: number[];
|
||||
sampleEligibleButMissingNoteIds: number[];
|
||||
topMissingAuthorMemberRecIds: Array<{ memberRecId: number; count: number }>;
|
||||
topMissingParentTicketIds: Array<{ srServiceRecId: number; count: number }>;
|
||||
automateApi: {
|
||||
total: number;
|
||||
eligibleForSync: number;
|
||||
missingSrServiceRecId: number;
|
||||
missingMemberRecId: number;
|
||||
missingParentTicketInApi: number;
|
||||
missingAuthorMappingInApi: number;
|
||||
eligibleButMissingInApi: number;
|
||||
sampleMemberRecIds: number[];
|
||||
sampleNoteIdsMissingInApi: number[];
|
||||
};
|
||||
};
|
||||
|
||||
const isAutomateApiAuthor = (
|
||||
createdBy: string | null,
|
||||
originalAuthor: string | null
|
||||
): boolean => {
|
||||
const normalizedCreatedBy = createdBy?.trim().toLowerCase() ?? "";
|
||||
const normalizedOriginalAuthor = originalAuthor?.trim().toLowerCase() ?? "";
|
||||
return (
|
||||
normalizedCreatedBy.includes("automateapi") ||
|
||||
normalizedOriginalAuthor.includes("automateapi")
|
||||
);
|
||||
};
|
||||
|
||||
const parseEnvFile = (path: string): EnvMap => {
|
||||
const envData = readFileSync(path, "utf8");
|
||||
const out: EnvMap = {};
|
||||
|
||||
for (const rawLine of envData.split(/\r?\n/)) {
|
||||
const line = rawLine.trim();
|
||||
if (!line || line.startsWith("#")) continue;
|
||||
|
||||
const index = line.indexOf("=");
|
||||
if (index <= 0) continue;
|
||||
|
||||
const key = line.slice(0, index).trim();
|
||||
let value = line.slice(index + 1).trim();
|
||||
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
out[key] = value;
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
const readApiEnv = (): EnvMap => {
|
||||
const candidates = [
|
||||
resolve(import.meta.dir, "../api/.env"),
|
||||
resolve(process.cwd(), "../api/.env"),
|
||||
resolve(process.cwd(), "api/.env"),
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
return parseEnvFile(candidate);
|
||||
} catch {
|
||||
// Try next
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
const apiEnv = readApiEnv();
|
||||
|
||||
const cwDatabaseUrl =
|
||||
process.env.CW_DATABASE_URL ||
|
||||
process.env.DATABASE_URL ||
|
||||
apiEnv.CW_DATABASE_URL;
|
||||
const apiDatabaseUrl =
|
||||
process.env.API_DATABASE_URL ||
|
||||
process.env.OPTIMA_API_DATABASE_URL ||
|
||||
apiEnv.API_DATABASE_URL ||
|
||||
apiEnv.OPTIMA_API_DATABASE_URL ||
|
||||
apiEnv.DATABASE_URL;
|
||||
|
||||
if (!cwDatabaseUrl) {
|
||||
throw new Error("Missing CW DB URL. Set CW_DATABASE_URL or DATABASE_URL.");
|
||||
}
|
||||
|
||||
if (!apiDatabaseUrl) {
|
||||
throw new Error(
|
||||
"Missing API DB URL. Set API_DATABASE_URL/OPTIMA_API_DATABASE_URL or provide api/.env DATABASE_URL."
|
||||
);
|
||||
}
|
||||
|
||||
const cwPrisma = new CwPrismaClient({
|
||||
adapter: new PrismaMssql(cwDatabaseUrl),
|
||||
});
|
||||
const apiPrisma = new ApiPrismaClient({
|
||||
adapter: new PrismaPg({ connectionString: apiDatabaseUrl }),
|
||||
});
|
||||
|
||||
try {
|
||||
console.log("[diag] Loading API reference sets...");
|
||||
const [apiNotes, apiTickets, apiUsers] = await Promise.all([
|
||||
apiPrisma.serviceTicketNote.findMany({ select: { id: true } }),
|
||||
apiPrisma.serviceTicket.findMany({ select: { id: true } }),
|
||||
apiPrisma.user.findMany({ select: { cwMemberId: true } }),
|
||||
]);
|
||||
|
||||
const apiNoteIds = new Set<number>(apiNotes.map((r) => r.id));
|
||||
const apiTicketIds = new Set<number>(apiTickets.map((r) => r.id));
|
||||
const apiUserMemberIds = new Set<number>(
|
||||
apiUsers
|
||||
.map((r) => r.cwMemberId)
|
||||
.filter((v): v is number => Number.isInteger(v))
|
||||
);
|
||||
|
||||
console.log(
|
||||
`[diag] API sets: notes=${apiNoteIds.size} tickets=${apiTicketIds.size} usersWithCwMemberId=${apiUserMemberIds.size}`
|
||||
);
|
||||
|
||||
const cwTotal = await cwPrisma.ticketNote.count();
|
||||
const apiTotal = apiNoteIds.size;
|
||||
|
||||
let missingSrServiceRecId = 0;
|
||||
let missingMemberRecId = 0;
|
||||
let missingParentTicketInApi = 0;
|
||||
let missingAuthorMappingInApi = 0;
|
||||
let eligibleForSync = 0;
|
||||
let eligibleButMissingInApi = 0;
|
||||
|
||||
let automateTotal = 0;
|
||||
let automateEligibleForSync = 0;
|
||||
let automateMissingSrServiceRecId = 0;
|
||||
let automateMissingMemberRecId = 0;
|
||||
let automateMissingParentTicketInApi = 0;
|
||||
let automateMissingAuthorMappingInApi = 0;
|
||||
let automateEligibleButMissingInApi = 0;
|
||||
|
||||
const sampleMissingParentTicketIds: number[] = [];
|
||||
const sampleMissingAuthorMemberRecIds: number[] = [];
|
||||
const sampleEligibleButMissingNoteIds: number[] = [];
|
||||
const automateSampleMemberRecIds: number[] = [];
|
||||
const automateSampleNoteIdsMissingInApi: number[] = [];
|
||||
const missingAuthorCounts = new Map<number, number>();
|
||||
const missingParentCounts = new Map<number, number>();
|
||||
|
||||
let cursor = 0;
|
||||
const batchSize = 5000;
|
||||
|
||||
console.log(
|
||||
`[diag] Scanning CW TicketNote rows in batches of ${batchSize}...`
|
||||
);
|
||||
|
||||
while (true) {
|
||||
const batch = await cwPrisma.ticketNote.findMany({
|
||||
where: {
|
||||
ticketNoteRecId: {
|
||||
gt: cursor,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
ticketNoteRecId: "asc",
|
||||
},
|
||||
select: {
|
||||
ticketNoteRecId: true,
|
||||
srServiceRecId: true,
|
||||
memberRecId: true,
|
||||
createdBy: true,
|
||||
originalAuthor: true,
|
||||
},
|
||||
take: batchSize,
|
||||
});
|
||||
|
||||
if (batch.length === 0) break;
|
||||
|
||||
for (const row of batch) {
|
||||
const noteId = row.ticketNoteRecId;
|
||||
const srServiceRecId = row.srServiceRecId;
|
||||
const memberRecId = row.memberRecId;
|
||||
const isAutomate = isAutomateApiAuthor(
|
||||
row.createdBy,
|
||||
row.originalAuthor
|
||||
);
|
||||
|
||||
if (isAutomate) {
|
||||
automateTotal++;
|
||||
if (
|
||||
memberRecId &&
|
||||
automateSampleMemberRecIds.length < 20 &&
|
||||
!automateSampleMemberRecIds.includes(memberRecId)
|
||||
) {
|
||||
automateSampleMemberRecIds.push(memberRecId);
|
||||
}
|
||||
}
|
||||
|
||||
let blocked = false;
|
||||
|
||||
if (!srServiceRecId) {
|
||||
missingSrServiceRecId++;
|
||||
if (isAutomate) {
|
||||
automateMissingSrServiceRecId++;
|
||||
}
|
||||
blocked = true;
|
||||
} else if (!apiTicketIds.has(srServiceRecId)) {
|
||||
missingParentTicketInApi++;
|
||||
if (isAutomate) {
|
||||
automateMissingParentTicketInApi++;
|
||||
}
|
||||
blocked = true;
|
||||
missingParentCounts.set(
|
||||
srServiceRecId,
|
||||
(missingParentCounts.get(srServiceRecId) ?? 0) + 1
|
||||
);
|
||||
if (sampleMissingParentTicketIds.length < 20) {
|
||||
sampleMissingParentTicketIds.push(srServiceRecId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!memberRecId) {
|
||||
missingMemberRecId++;
|
||||
if (isAutomate) {
|
||||
automateMissingMemberRecId++;
|
||||
}
|
||||
blocked = true;
|
||||
} else if (!apiUserMemberIds.has(memberRecId)) {
|
||||
missingAuthorMappingInApi++;
|
||||
if (isAutomate) {
|
||||
automateMissingAuthorMappingInApi++;
|
||||
}
|
||||
blocked = true;
|
||||
missingAuthorCounts.set(
|
||||
memberRecId,
|
||||
(missingAuthorCounts.get(memberRecId) ?? 0) + 1
|
||||
);
|
||||
if (sampleMissingAuthorMemberRecIds.length < 20) {
|
||||
sampleMissingAuthorMemberRecIds.push(memberRecId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!blocked) {
|
||||
eligibleForSync++;
|
||||
if (isAutomate) {
|
||||
automateEligibleForSync++;
|
||||
}
|
||||
if (!apiNoteIds.has(noteId)) {
|
||||
eligibleButMissingInApi++;
|
||||
if (isAutomate) {
|
||||
automateEligibleButMissingInApi++;
|
||||
if (automateSampleNoteIdsMissingInApi.length < 20) {
|
||||
automateSampleNoteIdsMissingInApi.push(noteId);
|
||||
}
|
||||
}
|
||||
if (sampleEligibleButMissingNoteIds.length < 50) {
|
||||
sampleEligibleButMissingNoteIds.push(noteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor = batch[batch.length - 1]!.ticketNoteRecId;
|
||||
if (cursor % 50000 < batchSize) {
|
||||
console.log(`[diag] Progress cursor=${cursor}`);
|
||||
}
|
||||
}
|
||||
|
||||
const summary: Summary = {
|
||||
cwTotal,
|
||||
apiTotal,
|
||||
eligibleForSync,
|
||||
missingSrServiceRecId,
|
||||
missingMemberRecId,
|
||||
missingParentTicketInApi,
|
||||
missingAuthorMappingInApi,
|
||||
eligibleButMissingInApi,
|
||||
sampleMissingParentTicketIds,
|
||||
sampleMissingAuthorMemberRecIds,
|
||||
sampleEligibleButMissingNoteIds,
|
||||
topMissingAuthorMemberRecIds: [...missingAuthorCounts.entries()]
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 20)
|
||||
.map(([memberRecId, count]) => ({ memberRecId, count })),
|
||||
topMissingParentTicketIds: [...missingParentCounts.entries()]
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 20)
|
||||
.map(([srServiceRecId, count]) => ({ srServiceRecId, count })),
|
||||
automateApi: {
|
||||
total: automateTotal,
|
||||
eligibleForSync: automateEligibleForSync,
|
||||
missingSrServiceRecId: automateMissingSrServiceRecId,
|
||||
missingMemberRecId: automateMissingMemberRecId,
|
||||
missingParentTicketInApi: automateMissingParentTicketInApi,
|
||||
missingAuthorMappingInApi: automateMissingAuthorMappingInApi,
|
||||
eligibleButMissingInApi: automateEligibleButMissingInApi,
|
||||
sampleMemberRecIds: automateSampleMemberRecIds,
|
||||
sampleNoteIdsMissingInApi: automateSampleNoteIdsMissingInApi,
|
||||
},
|
||||
};
|
||||
|
||||
console.log("[diag] TicketNote sync gap summary:");
|
||||
console.log(JSON.stringify(summary, null, 2));
|
||||
} finally {
|
||||
await Promise.all([cwPrisma.$disconnect(), apiPrisma.$disconnect()]);
|
||||
}
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("[diag] Failed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user