import { and, eq, isNotNull } from "drizzle-orm"; import { assertProjectSettingsUpdateProjectCommand, createProjectSettingsUpdateProjectCommand, type ProjectSettingsValues, } from "../../domain/models/project-settings-project-command.model.js"; import type { ExecuteProjectSettingsUpdateCommandInput, ProjectSettingsProjectCommandStore, } from "../../domain/ports/project-settings-project-command.store.js"; import type { AppDatabase } from "../database-context.js"; import { projects } from "../schema/projects.js"; import { distributionBoards } from "../schema/distribution-boards.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; import { updateAllDerivedProjectVoltages } from "./project-voltage.persistence.js"; export class ProjectSettingsProjectCommandRepository implements ProjectSettingsProjectCommandStore { constructor(private readonly database: AppDatabase) {} executeUpdate(input: ExecuteProjectSettingsUpdateCommandInput) { assertProjectSettingsUpdateProjectCommand(input.command); return executeProjectCommandTransaction( this.database, input, (tx) => this.applyCommand(tx, input) ); } private applyCommand( tx: AppDatabase, input: ExecuteProjectSettingsUpdateCommandInput ) { const current = tx .select() .from(projects) .where(eq(projects.id, input.projectId)) .get(); if (!current) { throw new Error("Project not found."); } const target: ProjectSettingsValues = input.command.schemaVersion === 1 ? { ...input.command.payload, isPublicBuilding: current.isPublicBuilding, } : input.command.payload; if (projectSettingsEqual(current, target)) { throw new Error("Project settings did not change."); } const inverseValues: ProjectSettingsValues = { name: current.name, internalProjectNumber: current.internalProjectNumber, externalProjectNumber: current.externalProjectNumber, buildingOwner: current.buildingOwner, description: current.description, isPublicBuilding: current.isPublicBuilding, singlePhaseVoltageV: current.singlePhaseVoltageV, threePhaseVoltageV: current.threePhaseVoltageV, enabledDistributionBoardSupplyTypes: current.enabledDistributionBoardSupplyTypes, }; const inverse = input.command.schemaVersion === 1 ? createBaselineProjectSettingsUpdateCommand(inverseValues) : createProjectSettingsUpdateProjectCommand(inverseValues); assertDisabledSupplyTypesAreUnused(tx, input.projectId, target); const updated = tx .update(projects) .set(target) .where(eq(projects.id, input.projectId)) .run(); if (updated.changes !== 1) { throw new Error("Project changed before settings update."); } updateAllDerivedProjectVoltages(tx, input.projectId); return inverse; } } function createBaselineProjectSettingsUpdateCommand( values: ProjectSettingsValues ) { return { schemaVersion: 1 as const, type: "project.update-settings" as const, payload: { name: values.name, internalProjectNumber: values.internalProjectNumber, externalProjectNumber: values.externalProjectNumber, buildingOwner: values.buildingOwner, description: values.description, singlePhaseVoltageV: values.singlePhaseVoltageV, threePhaseVoltageV: values.threePhaseVoltageV, enabledDistributionBoardSupplyTypes: values.enabledDistributionBoardSupplyTypes, }, }; } function projectSettingsEqual( current: ProjectSettingsValues, target: ProjectSettingsValues ) { return ( current.name === target.name && current.internalProjectNumber === target.internalProjectNumber && current.externalProjectNumber === target.externalProjectNumber && current.buildingOwner === target.buildingOwner && current.description === target.description && current.isPublicBuilding === target.isPublicBuilding && current.singlePhaseVoltageV === target.singlePhaseVoltageV && current.threePhaseVoltageV === target.threePhaseVoltageV && sameSupplyTypes( current.enabledDistributionBoardSupplyTypes, target.enabledDistributionBoardSupplyTypes ) ); } function sameSupplyTypes( left: ProjectSettingsValues["enabledDistributionBoardSupplyTypes"], right: ProjectSettingsValues["enabledDistributionBoardSupplyTypes"] ) { return ( left.length === right.length && left.every((value, index) => value === right[index]) ); } function assertDisabledSupplyTypesAreUnused( database: AppDatabase, projectId: string, target: ProjectSettingsValues ) { const used = database .select({ supplyType: distributionBoards.supplyType }) .from(distributionBoards) .where( and( eq(distributionBoards.projectId, projectId), isNotNull(distributionBoards.supplyType) ) ) .all(); const disabledUsedTypes = [ ...new Set( used .map((entry) => entry.supplyType) .filter( (supplyType) => supplyType !== null && !target.enabledDistributionBoardSupplyTypes.some( (enabled) => enabled === supplyType ) ) ), ]; if (disabledUsedTypes.length > 0) { throw new Error( `Verwendete Netzarten können nicht deaktiviert werden: ${disabledUsedTypes.join(", ")}.` ); } }