232 lines
7.0 KiB
TypeScript
232 lines
7.0 KiB
TypeScript
import {
|
|
createProjectDeviceRowSyncProjectCommand,
|
|
projectDeviceSyncRowSnapshotFields,
|
|
type ProjectDeviceRowSyncProjectCommand,
|
|
type ProjectDeviceSyncRowSnapshot,
|
|
} from "../models/project-device-row-sync-project-command.model.js";
|
|
import {
|
|
projectDeviceSyncFields,
|
|
type ProjectDeviceSyncField,
|
|
} from "../../shared/constants/project-device-sync-fields.js";
|
|
import {
|
|
parseOverriddenFields,
|
|
serializeOverriddenFields,
|
|
} from "./project-device-overrides.js";
|
|
|
|
export {
|
|
parseOverriddenFields,
|
|
serializeOverriddenFields,
|
|
} from "./project-device-overrides.js";
|
|
|
|
export type ProjectDeviceSyncSource = {
|
|
id: string;
|
|
} & Pick<ProjectDeviceSyncRowSnapshot, ProjectDeviceSyncField>;
|
|
|
|
export type LinkedProjectDeviceRow = ProjectDeviceSyncRowSnapshot & {
|
|
id: string;
|
|
circuitId: string;
|
|
equipmentIdentifier: string;
|
|
circuitDisplayName: string | null;
|
|
circuitListId: string;
|
|
circuitListName: string;
|
|
distributionBoardId: string;
|
|
distributionBoardName: string;
|
|
};
|
|
|
|
export interface ProjectDeviceSyncSourceReader {
|
|
findById(
|
|
projectId: string,
|
|
projectDeviceId: string
|
|
): Promise<ProjectDeviceSyncSource | null>;
|
|
}
|
|
|
|
export interface LinkedProjectDeviceRowReader {
|
|
listLinkedByProjectDevice(
|
|
projectId: string,
|
|
projectDeviceId: string
|
|
): Promise<LinkedProjectDeviceRow[]>;
|
|
}
|
|
|
|
export interface ProjectDeviceSyncDependencies {
|
|
projectDeviceRepository: ProjectDeviceSyncSourceReader;
|
|
deviceRowRepository: LinkedProjectDeviceRowReader;
|
|
}
|
|
|
|
function sourceValue(
|
|
projectDevice: ProjectDeviceSyncSource,
|
|
field: ProjectDeviceSyncField
|
|
) {
|
|
return projectDevice[field];
|
|
}
|
|
|
|
function valuesEqual(left: unknown, right: unknown) {
|
|
return (left ?? null) === (right ?? null);
|
|
}
|
|
|
|
function buildDifferences(
|
|
projectDevice: ProjectDeviceSyncSource,
|
|
row: LinkedProjectDeviceRow
|
|
) {
|
|
return projectDeviceSyncFields
|
|
.filter((field) => !valuesEqual(row[field], sourceValue(projectDevice, field)))
|
|
.map((field) => ({
|
|
field,
|
|
currentValue: row[field] ?? null,
|
|
sourceValue: sourceValue(projectDevice, field) ?? null,
|
|
isOverridden: parseOverriddenFields(row.overriddenFields).includes(field),
|
|
}));
|
|
}
|
|
|
|
export class ProjectDeviceSyncService {
|
|
private readonly projectDeviceRepository: ProjectDeviceSyncSourceReader;
|
|
private readonly deviceRowRepository: LinkedProjectDeviceRowReader;
|
|
|
|
constructor(deps: ProjectDeviceSyncDependencies) {
|
|
this.projectDeviceRepository = deps.projectDeviceRepository;
|
|
this.deviceRowRepository = deps.deviceRowRepository;
|
|
}
|
|
|
|
async getPreview(projectId: string, projectDeviceId: string) {
|
|
const projectDevice = await this.projectDeviceRepository.findById(projectId, projectDeviceId);
|
|
if (!projectDevice) {
|
|
throw new Error("Project device not found.");
|
|
}
|
|
const rows = await this.deviceRowRepository.listLinkedByProjectDevice(projectId, projectDeviceId);
|
|
|
|
return {
|
|
projectDevice: {
|
|
id: projectDevice.id,
|
|
name: projectDevice.name,
|
|
displayName: projectDevice.displayName,
|
|
},
|
|
rows: rows.map((row) => ({
|
|
rowId: row.id,
|
|
circuitId: row.circuitId,
|
|
equipmentIdentifier: row.equipmentIdentifier,
|
|
circuitDisplayName: row.circuitDisplayName,
|
|
circuitListId: row.circuitListId,
|
|
circuitListName: row.circuitListName,
|
|
distributionBoardId: row.distributionBoardId,
|
|
distributionBoardName: row.distributionBoardName,
|
|
rowDisplayName: row.displayName,
|
|
overriddenFields: parseOverriddenFields(row.overriddenFields),
|
|
differences: buildDifferences(projectDevice, row),
|
|
})),
|
|
};
|
|
}
|
|
|
|
async createSynchronizeCommand(
|
|
projectId: string,
|
|
projectDeviceId: string,
|
|
rowIds: string[],
|
|
fields: ProjectDeviceSyncField[]
|
|
): Promise<ProjectDeviceRowSyncProjectCommand> {
|
|
const projectDevice = await this.projectDeviceRepository.findById(projectId, projectDeviceId);
|
|
if (!projectDevice) {
|
|
throw new Error("Project device not found.");
|
|
}
|
|
const linkedRows = await this.deviceRowRepository.listLinkedByProjectDevice(projectId, projectDeviceId);
|
|
const selectedRows = this.resolveSelectedRows(linkedRows, rowIds);
|
|
const selectedFields = [...new Set(fields)];
|
|
const assignments = selectedRows.flatMap((row) => {
|
|
const expected = toSyncSnapshot(row);
|
|
const target = { ...expected };
|
|
for (const field of selectedFields) {
|
|
Object.assign(target, {
|
|
[field]: sourceValue(projectDevice, field) ?? null,
|
|
});
|
|
}
|
|
const remainingOverrides = parseOverriddenFields(row.overriddenFields).filter(
|
|
(field) => !selectedFields.includes(field)
|
|
);
|
|
target.overriddenFields =
|
|
serializeOverriddenFields(remainingOverrides) ?? null;
|
|
return snapshotsEqual(expected, target)
|
|
? []
|
|
: [{ rowId: row.id, expected, target }];
|
|
});
|
|
|
|
return createProjectDeviceRowSyncProjectCommand(
|
|
projectDeviceId,
|
|
"synchronize",
|
|
assignments
|
|
);
|
|
}
|
|
|
|
async createDisconnectCommand(
|
|
projectId: string,
|
|
projectDeviceId: string,
|
|
rowIds: string[]
|
|
): Promise<ProjectDeviceRowSyncProjectCommand> {
|
|
const projectDevice = await this.projectDeviceRepository.findById(
|
|
projectId,
|
|
projectDeviceId
|
|
);
|
|
if (!projectDevice) {
|
|
throw new Error("Project device not found.");
|
|
}
|
|
const linkedRows = await this.deviceRowRepository.listLinkedByProjectDevice(projectId, projectDeviceId);
|
|
const selectedRows = this.resolveSelectedRows(linkedRows, rowIds);
|
|
return createProjectDeviceRowSyncProjectCommand(
|
|
projectDeviceId,
|
|
"disconnect",
|
|
selectedRows.map((row) => {
|
|
const expected = toSyncSnapshot(row);
|
|
return {
|
|
rowId: row.id,
|
|
expected,
|
|
target: {
|
|
...expected,
|
|
linkedProjectDeviceId: null,
|
|
},
|
|
};
|
|
})
|
|
);
|
|
}
|
|
|
|
private resolveSelectedRows(
|
|
linkedRows: LinkedProjectDeviceRow[],
|
|
rowIds: string[]
|
|
) {
|
|
const uniqueRowIds = [...new Set(rowIds)];
|
|
const byId = new Map(linkedRows.map((row) => [row.id, row]));
|
|
const selectedRows = uniqueRowIds
|
|
.map((rowId) => byId.get(rowId))
|
|
.filter(Boolean) as LinkedProjectDeviceRow[];
|
|
if (selectedRows.length !== uniqueRowIds.length) {
|
|
throw new Error("One or more rows are not linked to this project device.");
|
|
}
|
|
return selectedRows;
|
|
}
|
|
|
|
}
|
|
|
|
function toSyncSnapshot(
|
|
row: LinkedProjectDeviceRow
|
|
): ProjectDeviceSyncRowSnapshot {
|
|
return {
|
|
linkedProjectDeviceId: row.linkedProjectDeviceId,
|
|
name: row.name,
|
|
displayName: row.displayName,
|
|
phaseType: row.phaseType,
|
|
connectionKind: row.connectionKind,
|
|
costGroup: row.costGroup,
|
|
category: row.category,
|
|
quantity: row.quantity,
|
|
powerPerUnit: row.powerPerUnit,
|
|
simultaneityFactor: row.simultaneityFactor,
|
|
cosPhi: row.cosPhi,
|
|
remark: row.remark,
|
|
overriddenFields: row.overriddenFields,
|
|
};
|
|
}
|
|
|
|
function snapshotsEqual(
|
|
left: ProjectDeviceSyncRowSnapshot,
|
|
right: ProjectDeviceSyncRowSnapshot
|
|
) {
|
|
return projectDeviceSyncRowSnapshotFields.every(
|
|
(field) => left[field] === right[field]
|
|
);
|
|
}
|