Decouple legacy migration service
This commit is contained in:
@@ -1,25 +1,19 @@
|
||||
import { CircuitRepository } from "../../db/repositories/circuit.repository.js";
|
||||
import { CircuitListRepository } from "../../db/repositories/circuit-list.repository.js";
|
||||
import { CircuitSectionRepository } from "../../db/repositories/circuit-section.repository.js";
|
||||
import {
|
||||
LegacyConsumerMigrationRepository,
|
||||
type LegacyMigrationCircuitPersistenceInput,
|
||||
} from "../../db/repositories/legacy-consumer-migration.repository.js";
|
||||
import { RoomRepository } from "../../db/repositories/room.repository.js";
|
||||
import type { LegacyMigrationReport } from "../models/circuit-tree.model.js";
|
||||
import type {
|
||||
LegacyConsumerMigrationDependencies,
|
||||
LegacyConsumerSource,
|
||||
LegacyMigrationCircuitInput,
|
||||
} from "../ports/legacy-consumer-migration.store.js";
|
||||
import {
|
||||
inferSectionKeyFromEquipmentIdentifier,
|
||||
inferSectionKeyFromLegacyInput,
|
||||
normalizeCircuitNumber,
|
||||
} from "./legacy-consumer-migration-planner.js";
|
||||
|
||||
type LegacyConsumerRow = Awaited<
|
||||
ReturnType<
|
||||
LegacyConsumerMigrationRepository["listSourceConsumersByCircuitList"]
|
||||
>
|
||||
>[number];
|
||||
|
||||
function parseEquipmentSequence(equipmentIdentifier: string, prefix: string): number | null {
|
||||
function parseEquipmentSequence(
|
||||
equipmentIdentifier: string,
|
||||
prefix: string
|
||||
): number | null {
|
||||
if (!equipmentIdentifier.startsWith(prefix)) {
|
||||
return null;
|
||||
}
|
||||
@@ -30,43 +24,45 @@ function parseEquipmentSequence(equipmentIdentifier: string, prefix: string): nu
|
||||
return Number(suffix);
|
||||
}
|
||||
|
||||
|
||||
export class LegacyConsumerMigrationService {
|
||||
private readonly circuitListRepository = new CircuitListRepository();
|
||||
private readonly sectionRepository = new CircuitSectionRepository();
|
||||
private readonly circuitRepository = new CircuitRepository();
|
||||
private readonly roomRepository = new RoomRepository();
|
||||
|
||||
constructor(
|
||||
private readonly migrationRepository: LegacyConsumerMigrationRepository
|
||||
private readonly dependencies: LegacyConsumerMigrationDependencies
|
||||
) {}
|
||||
|
||||
async migrateCircuitList(projectId: string, circuitListId: string): Promise<LegacyMigrationReport> {
|
||||
async migrateCircuitList(
|
||||
projectId: string,
|
||||
circuitListId: string
|
||||
): Promise<LegacyMigrationReport> {
|
||||
// Migration is additive: legacy consumers are preserved, and circuit-first entities are created
|
||||
// with mapping records so transition remains auditable and reversible.
|
||||
const list = await this.circuitListRepository.findById(projectId, circuitListId);
|
||||
const list = await this.dependencies.circuitListReader.findById(
|
||||
projectId,
|
||||
circuitListId
|
||||
);
|
||||
if (!list) {
|
||||
throw new Error("Circuit list not found in project.");
|
||||
}
|
||||
|
||||
await this.sectionRepository.createDefaults(circuitListId);
|
||||
const sections = await this.sectionRepository.listByCircuitList(circuitListId);
|
||||
await this.dependencies.sectionStore.createDefaults(circuitListId);
|
||||
const sections =
|
||||
await this.dependencies.sectionStore.listByCircuitList(circuitListId);
|
||||
const sectionByKey = new Map(sections.map((section) => [section.key, section]));
|
||||
const unassignedSection = sectionByKey.get("unassigned");
|
||||
if (!unassignedSection) {
|
||||
throw new Error("Unassigned section is required.");
|
||||
}
|
||||
|
||||
const existingCircuits = await this.circuitRepository.listByCircuitList(circuitListId);
|
||||
const existingCircuits =
|
||||
await this.dependencies.circuitReader.listByCircuitList(circuitListId);
|
||||
const usedEquipmentIdentifiers = new Set(
|
||||
existingCircuits.map((circuit) => circuit.equipmentIdentifier.toUpperCase())
|
||||
);
|
||||
|
||||
const legacyConsumers =
|
||||
await this.migrationRepository.listSourceConsumersByCircuitList(
|
||||
await this.dependencies.migrationStore.listSourceConsumersByCircuitList(
|
||||
circuitListId
|
||||
);
|
||||
const rooms = await this.roomRepository.listByProject(projectId);
|
||||
const rooms = await this.dependencies.roomReader.listByProject(projectId);
|
||||
const roomById = new Map(rooms.map((room) => [room.id, room]));
|
||||
|
||||
const report: LegacyMigrationReport = {
|
||||
@@ -82,14 +78,16 @@ export class LegacyConsumerMigrationService {
|
||||
|
||||
// Idempotency guard: skip consumers already mapped in previous migration run.
|
||||
const migratedConsumerIds = new Set(
|
||||
await this.migrationRepository.listMigratedConsumerIds(circuitListId)
|
||||
await this.dependencies.migrationStore.listMigratedConsumerIds(
|
||||
circuitListId
|
||||
)
|
||||
);
|
||||
const consumersToMigrate = legacyConsumers.filter((consumer) => !migratedConsumerIds.has(consumer.id));
|
||||
|
||||
// Legacy rows are grouped by normalized circuit number so duplicates become
|
||||
// multiple device rows within one circuit instead of duplicate circuits.
|
||||
const byNormalizedCircuitNumber = new Map<string, LegacyConsumerRow[]>();
|
||||
const withoutNormalizedCircuitNumber: LegacyConsumerRow[] = [];
|
||||
const byNormalizedCircuitNumber = new Map<string, LegacyConsumerSource[]>();
|
||||
const withoutNormalizedCircuitNumber: LegacyConsumerSource[] = [];
|
||||
for (const consumer of consumersToMigrate) {
|
||||
const normalized = normalizeCircuitNumber(consumer.circuitNumber ?? null);
|
||||
if (!normalized) {
|
||||
@@ -110,7 +108,7 @@ export class LegacyConsumerMigrationService {
|
||||
|
||||
const groups: Array<{
|
||||
equipmentIdentifier: string | null;
|
||||
consumers: LegacyConsumerRow[];
|
||||
consumers: LegacyConsumerSource[];
|
||||
inferredSectionKey: string | null;
|
||||
isGeneratedIdentifier: boolean;
|
||||
}> = [];
|
||||
@@ -152,7 +150,7 @@ export class LegacyConsumerMigrationService {
|
||||
maxBySectionPrefix.set(section.prefix.toUpperCase(), Math.max(current, sequence));
|
||||
}
|
||||
|
||||
const migrationCircuits: LegacyMigrationCircuitPersistenceInput[] = [];
|
||||
const migrationCircuits: LegacyMigrationCircuitInput[] = [];
|
||||
for (const group of groups) {
|
||||
const representative = group.consumers[0];
|
||||
let section = group.inferredSectionKey ? sectionByKey.get(group.inferredSectionKey) : null;
|
||||
@@ -181,7 +179,7 @@ export class LegacyConsumerMigrationService {
|
||||
|
||||
usedEquipmentIdentifiers.add(equipmentIdentifier.toUpperCase());
|
||||
|
||||
const deviceRows: LegacyMigrationCircuitPersistenceInput["deviceRows"] = [];
|
||||
const deviceRows: LegacyMigrationCircuitInput["deviceRows"] = [];
|
||||
const circuitSortOrder = nextSortOrder;
|
||||
nextSortOrder += 10;
|
||||
|
||||
@@ -250,7 +248,7 @@ export class LegacyConsumerMigrationService {
|
||||
report.warnings.push(`Consumer ${row.consumerId} was migrated into unassigned section.`);
|
||||
}
|
||||
|
||||
this.migrationRepository.persistCircuitListMigration({
|
||||
this.dependencies.migrationStore.persistCircuitListMigration({
|
||||
circuitListId,
|
||||
circuits: migrationCircuits,
|
||||
report: {
|
||||
|
||||
Reference in New Issue
Block a user