Add external row quantity foundation

This commit is contained in:
2026-08-02 18:26:22 +02:00
parent bc2bc7ece5
commit dac19b093c
30 changed files with 2750 additions and 22 deletions
@@ -18,6 +18,7 @@ export interface CircuitDeviceRowUpdateValues {
roomNumberSnapshot: string | null;
roomNameSnapshot: string | null;
quantity: number;
manualQuantity: number;
powerPerUnit: number;
simultaneityFactor: number;
cosPhi: number | null;
@@ -51,7 +52,18 @@ export function createCircuitDeviceRowUpdateProjectCommand(
rowId: string,
patch: CircuitDeviceRowUpdatePatch
): CircuitDeviceRowUpdateProjectCommand {
const changes = Object.entries(patch).map(([field, value]) => ({
const normalizedPatch =
patch.quantity !== undefined && patch.manualQuantity === undefined
? { ...patch, manualQuantity: patch.quantity }
: patch;
if (
normalizedPatch.quantity !== undefined &&
normalizedPatch.manualQuantity !== undefined &&
normalizedPatch.manualQuantity > normalizedPatch.quantity
) {
throw new Error("manualQuantity must not exceed quantity.");
}
const changes = Object.entries(normalizedPatch).map(([field, value]) => ({
field: field as CircuitDeviceRowUpdateField,
value,
})) as CircuitDeviceRowUpdateFieldChange[];
@@ -125,6 +137,7 @@ function assertCircuitDeviceRowUpdateFieldValue(
}
if (
field === "quantity" ||
field === "manualQuantity" ||
field === "powerPerUnit" ||
field === "simultaneityFactor"
) {
@@ -164,6 +177,7 @@ function isCircuitDeviceRowUpdateField(
"roomNumberSnapshot",
"roomNameSnapshot",
"quantity",
"manualQuantity",
"powerPerUnit",
"simultaneityFactor",
"cosPhi",
@@ -22,6 +22,7 @@ export interface CircuitDeviceRowSnapshot {
roomNumberSnapshot: string | null;
roomNameSnapshot: string | null;
quantity: number;
manualQuantity?: number;
powerPerUnit: number;
simultaneityFactor: number;
cosPhi: number | null;
@@ -57,10 +58,14 @@ export type CircuitDeviceRowStructureProjectCommand =
export function createCircuitDeviceRowInsertProjectCommand(
row: CircuitDeviceRowSnapshot
): CircuitDeviceRowInsertProjectCommand {
const normalizedRow = {
...row,
manualQuantity: row.manualQuantity ?? row.quantity,
};
const command: CircuitDeviceRowInsertProjectCommand = {
schemaVersion: circuitDeviceRowStructureCommandSchemaVersion,
type: circuitDeviceRowInsertCommandType,
payload: { row },
payload: { row: normalizedRow },
};
assertCircuitDeviceRowInsertProjectCommand(command);
return command;
@@ -117,7 +122,13 @@ export function assertCircuitDeviceRowInsertProjectCommand(
] as const) {
assertNullableString(row[field], `row.${field}`);
}
assertNonNegativeNumber(row.quantity, "row.quantity");
const quantity = row.quantity;
assertNonNegativeNumber(quantity, "row.quantity");
const manualQuantity = row.manualQuantity ?? quantity;
assertNonNegativeNumber(manualQuantity, "row.manualQuantity");
if (manualQuantity > quantity) {
throw new Error("row.manualQuantity must not exceed row.quantity.");
}
assertNonNegativeNumber(row.powerPerUnit, "row.powerPerUnit");
assertNonNegativeNumber(
row.simultaneityFactor,
@@ -172,7 +183,10 @@ function assertFiniteNumber(
}
}
function assertNonNegativeNumber(value: unknown, field: string) {
function assertNonNegativeNumber(
value: unknown,
field: string
): asserts value is number {
assertFiniteNumber(value, field);
if (value < 0) {
throw new Error(`${field} must not be negative.`);
@@ -14,6 +14,7 @@ export interface CircuitDeviceRow {
roomNumberSnapshot?: string;
roomNameSnapshot?: string;
quantity: number;
manualQuantity: number;
powerPerUnit: number;
simultaneityFactor: number;
cosPhi?: number;
+1
View File
@@ -13,6 +13,7 @@ export interface CircuitTreeDeviceRow {
roomNumberSnapshot?: string;
roomNameSnapshot?: string;
quantity: number;
manualQuantity: number;
powerPerUnit: number;
simultaneityFactor: number;
cosPhi?: number;
@@ -27,8 +27,9 @@ import {
type ExternalModelStateSnapshot,
} from "../../external-model/domain/external-model-contracts.js";
export const projectStateSnapshotSchemaVersion = 4 as const;
const previousProjectStateSnapshotSchemaVersion = 3 as const;
export const projectStateSnapshotSchemaVersion = 5 as const;
const previousProjectStateSnapshotSchemaVersion = 4 as const;
const externalModelProjectStateSnapshotSchemaVersion = 3 as const;
const legacyProjectStateSnapshotSchemaVersion = 2 as const;
const baselineProjectStateSnapshotSchemaVersion = 1 as const;
@@ -103,8 +104,15 @@ const circuitSectionSchema = z
}
});
const circuitDeviceRowSchema = z
.object({
const circuitDeviceRowSchema = z.preprocess(
(value) => {
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
const row = value as Record<string, unknown>;
return Object.prototype.hasOwnProperty.call(row, "manualQuantity")
? row
: { ...row, manualQuantity: row.quantity };
},
z.object({
id: idSchema,
circuitId: idSchema,
linkedProjectDeviceId: idSchema.nullable(),
@@ -120,13 +128,15 @@ const circuitDeviceRowSchema = z
roomNumberSnapshot: nullableStringSchema,
roomNameSnapshot: nullableStringSchema,
quantity: finiteNumberSchema.nonnegative(),
manualQuantity: finiteNumberSchema.nonnegative(),
powerPerUnit: finiteNumberSchema.nonnegative(),
simultaneityFactor: finiteNumberSchema.nonnegative(),
cosPhi: finiteNumberSchema.positive().nullable(),
remark: nullableStringSchema,
overriddenFields: nullableStringSchema,
})
.strict();
.strict()
);
const circuitSchema = z
.object({
@@ -453,6 +463,17 @@ const previousProjectStateSnapshotSchema = z
project: projectSchema,
distributionBoards: z.array(distributionBoardSchema),
externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(),
externalModel: externalModelStateSchema,
...projectStateSnapshotContents,
})
.strict();
const externalModelProjectStateSnapshotSchema = z
.object({
schemaVersion: z.literal(externalModelProjectStateSnapshotSchemaVersion),
project: projectSchema,
distributionBoards: z.array(distributionBoardSchema),
externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(),
...projectStateSnapshotContents,
})
.strict();
@@ -502,6 +523,13 @@ function upgradePreviousProjectStateSnapshot(value: unknown): unknown {
const schemaVersion = (value as { schemaVersion: number }).schemaVersion;
if (schemaVersion === previousProjectStateSnapshotSchemaVersion) {
const previous = previousProjectStateSnapshotSchema.parse(value);
return {
...previous,
schemaVersion: projectStateSnapshotSchemaVersion,
};
}
if (schemaVersion === externalModelProjectStateSnapshotSchemaVersion) {
const previous = externalModelProjectStateSnapshotSchema.parse(value);
return {
...previous,
schemaVersion: projectStateSnapshotSchemaVersion,
@@ -765,6 +793,11 @@ function assertProjectStateSnapshotRelations(
if (row.roomId !== null) {
assertReference(roomIds, row.roomId, "device row room");
}
if (row.manualQuantity > row.quantity) {
throw new Error(
"Snapshot device row manual quantity must not exceed total quantity."
);
}
}
}