feature: compare Kabellisten
This commit is contained in:
@@ -115,6 +115,7 @@ function buildSheetMappingStatus(sheetInfo: SheetInfo, headerRow: any[], rowInde
|
||||
});
|
||||
|
||||
return {
|
||||
sheetId: sheetInfo.id,
|
||||
sheetName: sheetInfo.name, // Für Anzeige
|
||||
headerRowIndex: rowIndex,
|
||||
availableColumns,
|
||||
@@ -424,3 +425,533 @@ export async function consolidateData(mappings: SheetMappingStatus[], settings:
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
|
||||
* Neue Funktion für den Vergleich von zwei Listen
|
||||
|
||||
*/
|
||||
|
||||
export async function compareData(
|
||||
|
||||
oldMapping: SheetMappingStatus,
|
||||
|
||||
newMapping: SheetMappingStatus,
|
||||
|
||||
settings: ConsolidateSettings
|
||||
|
||||
): Promise<number> {
|
||||
|
||||
return Excel.run(async (context) => {
|
||||
|
||||
// Hilfsfunktion zum Laden von Daten aus einem Mapping
|
||||
|
||||
const loadData = async (mapping: SheetMappingStatus): Promise<any[][]> => {
|
||||
|
||||
if (mapping.isExternal && mapping.externalData) {
|
||||
|
||||
// Return data starting after the header row
|
||||
|
||||
return mapping.externalData.slice(mapping.headerRowIndex + 1);
|
||||
|
||||
} else {
|
||||
|
||||
const sheet = context.workbook.worksheets.getItem(mapping.sheetName);
|
||||
|
||||
const usedRange = sheet.getUsedRange();
|
||||
|
||||
usedRange.load(["rowIndex", "rowCount", "text"]);
|
||||
|
||||
await context.sync();
|
||||
|
||||
|
||||
|
||||
const startRowIdx = usedRange.rowIndex;
|
||||
|
||||
const dataStartRowOffset = (mapping.headerRowIndex + 1) - startRowIdx;
|
||||
|
||||
|
||||
|
||||
if (dataStartRowOffset >= usedRange.rowCount || usedRange.rowCount === 0) {
|
||||
|
||||
return [];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Return data starting from the calculated offset
|
||||
|
||||
return usedRange.text.slice(dataStartRowOffset);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
const oldData = await loadData(oldMapping);
|
||||
|
||||
const newData = await loadData(newMapping);
|
||||
|
||||
|
||||
|
||||
// K-Nr Indices finden
|
||||
|
||||
const getColIndex = (mapping: SheetMappingStatus, targetId: string) => {
|
||||
|
||||
const m = mapping.mappings.find(m => m.targetColumn === targetId);
|
||||
|
||||
return m ? m.sourceColumnIndex : -1;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
const oldKNrIdx = getColIndex(oldMapping, "K-Nr.");
|
||||
|
||||
const newKNrIdx = getColIndex(newMapping, "K-Nr.");
|
||||
|
||||
|
||||
|
||||
if (oldKNrIdx === -1 || newKNrIdx === -1) {
|
||||
|
||||
throw new Error("K-Nr. Spalte muss in beiden Listen zugeordnet sein, um vergleichen zu können.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Zu vergleichende Spalten (ohne die Ausnahmen)
|
||||
|
||||
const columnsToCompare = TARGET_COLUMNS.filter(
|
||||
|
||||
tc => tc.id !== "K-Nr." && tc.id !== "Länge" && tc.id !== "gezogen am" && tc.id !== "von" // 'von' hier ist Monteur in Gesamtliste, aber TARGET_COLUMNS['von'] ist Start!
|
||||
|
||||
);
|
||||
|
||||
// Wir vergleichen ALLE gemappten Target-Columns, aber ignorieren (wie gefordert) Lnge/gezogen am/gezogen von,
|
||||
|
||||
// welche gar nicht in TARGET_COLUMNS sind! "von" und "nach" in TARGET_COLUMNS sind die Räume/Geräte.
|
||||
|
||||
// Das heißt wir ignorieren hier nichts extra aus TARGET_COLUMNS, da die spezifischen "gezogen am" etc Felder
|
||||
|
||||
// erst in der Consolidate Funktion hardcoded angehängt werden und gar nicht gemappt sind!
|
||||
|
||||
const allTargetCols = TARGET_COLUMNS.map(tc => tc.id);
|
||||
|
||||
|
||||
|
||||
// "von Raum" und "nach Raum" Indizes für Umverlegt-Check
|
||||
|
||||
const vonRaumId = "von Raum";
|
||||
|
||||
const nachRaumId = "nach Raum";
|
||||
|
||||
|
||||
|
||||
// Mapped Header für die Ausgabetabelle
|
||||
|
||||
const outputHeaders = ["K-Nr.", "Status", "Änderungsdetails", ...allTargetCols.filter(id => id !== "K-Nr.")];
|
||||
|
||||
|
||||
|
||||
// Maps erstellen
|
||||
|
||||
const oldMap = new Map<string, any[]>();
|
||||
|
||||
const oldDuplicates = new Set<string>();
|
||||
|
||||
|
||||
|
||||
for (const row of oldData) {
|
||||
|
||||
const knr = String(row[oldKNrIdx] || "").trim();
|
||||
|
||||
if (!knr) continue;
|
||||
|
||||
if (oldMap.has(knr)) {
|
||||
|
||||
oldDuplicates.add(knr);
|
||||
|
||||
}
|
||||
|
||||
oldMap.set(knr, row);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const newMap = new Map<string, any[]>();
|
||||
|
||||
const newDuplicates = new Set<string>();
|
||||
|
||||
|
||||
|
||||
for (const row of newData) {
|
||||
|
||||
const knr = String(row[newKNrIdx] || "").trim();
|
||||
|
||||
if (!knr) continue;
|
||||
|
||||
if (newMap.has(knr)) {
|
||||
|
||||
newDuplicates.add(knr);
|
||||
|
||||
}
|
||||
|
||||
newMap.set(knr, row);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const finalOutput: any[][] = [];
|
||||
|
||||
const formatQueue: { row: number, state: "added" | "removed" | "changed" | "moved", formatColRanges?: number[] }[] = [];
|
||||
|
||||
let currentRowIndex = 1; // 1-based, header is 0
|
||||
|
||||
|
||||
|
||||
// 1. Check old list against new list (Removed / Changed / Moved)
|
||||
|
||||
oldMap.forEach((oldRow, knr) => {
|
||||
|
||||
if (!newMap.has(knr)) {
|
||||
|
||||
// Entfernt
|
||||
|
||||
const outRow = [knr, "Entfernt", "Kabel fehlt in der neuen Liste"];
|
||||
|
||||
for (const colId of allTargetCols) {
|
||||
|
||||
if (colId === "K-Nr.") continue;
|
||||
|
||||
const idx = getColIndex(oldMapping, colId);
|
||||
|
||||
outRow.push(idx !== -1 ? oldRow[idx] : "");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (oldDuplicates.has(knr)) outRow[2] += " (Duplikat in alter Liste)";
|
||||
|
||||
|
||||
|
||||
finalOutput.push(outRow);
|
||||
|
||||
formatQueue.push({ row: currentRowIndex, state: "removed" });
|
||||
|
||||
currentRowIndex++;
|
||||
|
||||
} else {
|
||||
|
||||
// Existiert in beiden -> Vergleichen
|
||||
|
||||
const newRow = newMap.get(knr)!;
|
||||
|
||||
let isChanged = false;
|
||||
|
||||
let isMoved = false;
|
||||
|
||||
const changes: string[] = [];
|
||||
|
||||
const changedCols: number[] = [];
|
||||
|
||||
|
||||
|
||||
for (const colId of allTargetCols) {
|
||||
|
||||
if (colId === "K-Nr.") continue;
|
||||
|
||||
|
||||
|
||||
const oIdx = getColIndex(oldMapping, colId);
|
||||
|
||||
const nIdx = getColIndex(newMapping, colId);
|
||||
|
||||
|
||||
|
||||
const oVal = oIdx !== -1 ? String(oldRow[oIdx] || "").trim() : "";
|
||||
|
||||
const nVal = nIdx !== -1 ? String(newRow[nIdx] || "").trim() : "";
|
||||
|
||||
|
||||
|
||||
if (oVal !== nVal) {
|
||||
|
||||
isChanged = true;
|
||||
|
||||
changes.push(`${colId}: ${oVal || "Leer"} -> ${nVal || "Leer"}`);
|
||||
|
||||
|
||||
|
||||
// Output Header mapping: 0=KNr, 1=Status, 2=Details, 3... = the rest (offset by 3)
|
||||
|
||||
const outputColIdx = outputHeaders.indexOf(colId);
|
||||
|
||||
if (outputColIdx !== -1) {
|
||||
|
||||
changedCols.push(outputColIdx);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (colId === vonRaumId || colId === nachRaumId) {
|
||||
|
||||
isMoved = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (isChanged) {
|
||||
|
||||
const status = isMoved ? "Umverlegt" : "Geändert";
|
||||
|
||||
let details = changes.join(" | ");
|
||||
|
||||
|
||||
|
||||
if (oldDuplicates.has(knr) || newDuplicates.has(knr)) {
|
||||
|
||||
details += " (Duplikat gefunden!)";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const outRow = [knr, status, details];
|
||||
|
||||
for (const colId of allTargetCols) {
|
||||
|
||||
if (colId === "K-Nr.") continue;
|
||||
|
||||
const idx = getColIndex(newMapping, colId); // Zeigen die NEUEN Werte!
|
||||
|
||||
outRow.push(idx !== -1 ? newRow[idx] : "");
|
||||
|
||||
}
|
||||
|
||||
finalOutput.push(outRow);
|
||||
|
||||
formatQueue.push({ row: currentRowIndex, state: isMoved ? "moved" : "changed", formatColRanges: changedCols });
|
||||
|
||||
currentRowIndex++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 2. Check new list against old list (Added)
|
||||
|
||||
newMap.forEach((newRow, knr) => {
|
||||
|
||||
if (!oldMap.has(knr)) {
|
||||
|
||||
// Hinzugefügt
|
||||
|
||||
const outRow = [knr, "Neu", "Kabel neu hinzugefügt"];
|
||||
|
||||
for (const colId of allTargetCols) {
|
||||
|
||||
if (colId === "K-Nr.") continue;
|
||||
|
||||
const idx = getColIndex(newMapping, colId);
|
||||
|
||||
outRow.push(idx !== -1 ? newRow[idx] : "");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (newDuplicates.has(knr)) outRow[2] += " (Duplikat in neuer Liste)";
|
||||
|
||||
|
||||
|
||||
finalOutput.push(outRow);
|
||||
|
||||
formatQueue.push({ row: currentRowIndex, state: "added" });
|
||||
|
||||
currentRowIndex++;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 3. Wenn keine Änderungen
|
||||
|
||||
if (finalOutput.length === 0) {
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 4. In "Änderungsdokumentation" schreiben
|
||||
|
||||
const sheetName = "Änderungsdokumentation";
|
||||
|
||||
let targetSheet: Excel.Worksheet;
|
||||
|
||||
|
||||
|
||||
try {
|
||||
|
||||
targetSheet = context.workbook.worksheets.getItem(sheetName);
|
||||
|
||||
targetSheet.delete(); // Delete if exists to create a fresh one
|
||||
|
||||
await context.sync();
|
||||
|
||||
} catch (e) {
|
||||
|
||||
// Does not exist
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
targetSheet = context.workbook.worksheets.add(sheetName);
|
||||
|
||||
|
||||
|
||||
const totalRowsCount = finalOutput.length + 1; // +1 für Header
|
||||
|
||||
const totalColsCount = outputHeaders.length;
|
||||
|
||||
|
||||
|
||||
const targetRange = targetSheet.getRangeByIndexes(0, 0, totalRowsCount, totalColsCount);
|
||||
|
||||
const allValues = [outputHeaders, ...finalOutput];
|
||||
|
||||
|
||||
|
||||
const formatArray: string[][] = [];
|
||||
|
||||
for (let i = 0; i < totalRowsCount; i++) {
|
||||
|
||||
formatArray.push(new Array(totalColsCount).fill("@"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
targetRange.numberFormat = formatArray;
|
||||
|
||||
targetRange.values = allValues;
|
||||
|
||||
|
||||
|
||||
await context.sync();
|
||||
|
||||
|
||||
|
||||
const table = targetSheet.tables.add(targetRange, true /* hasHeaders */);
|
||||
|
||||
table.style = "TableStyleLight9";
|
||||
|
||||
table.showFilterButton = true;
|
||||
|
||||
|
||||
|
||||
await context.sync();
|
||||
|
||||
const bodyRange = table.getDataBodyRange();
|
||||
|
||||
|
||||
|
||||
// 5. Formate anwenden
|
||||
|
||||
for (const fmt of formatQueue) {
|
||||
|
||||
// row is 1-based (from data), bodyRange getRow is 0-based
|
||||
|
||||
const excelRow = bodyRange.getRow(fmt.row - 1);
|
||||
|
||||
|
||||
|
||||
if (fmt.state === "removed") {
|
||||
|
||||
excelRow.format.fill.color = settings.colorDeleted;
|
||||
|
||||
excelRow.format.font.strikethrough = true;
|
||||
|
||||
excelRow.format.font.color = "#990000"; // Dunkelrot
|
||||
|
||||
} else if (fmt.state === "added") {
|
||||
|
||||
excelRow.format.fill.color = settings.colorNew;
|
||||
|
||||
} else if (fmt.state === "moved") {
|
||||
|
||||
// Umverlegt z.B. hellblau oder lila (wir nehmen ein vordefiniertes Gelb oder passen settings an, hardcoded für jetzt)
|
||||
|
||||
excelRow.format.fill.color = "#d9edf7"; // Light Blue
|
||||
|
||||
|
||||
|
||||
// Einzelne Zellen markieren
|
||||
|
||||
if (fmt.formatColRanges) {
|
||||
|
||||
for (const col of fmt.formatColRanges) {
|
||||
|
||||
bodyRange.getCell(fmt.row - 1, col).format.fill.color = "#bce8f1"; // Stronger Blue
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if (fmt.state === "changed") {
|
||||
|
||||
excelRow.format.fill.color = settings.colorChanged;
|
||||
|
||||
|
||||
|
||||
// Einzelne Zellen markieren
|
||||
|
||||
if (fmt.formatColRanges) {
|
||||
|
||||
for (const col of fmt.formatColRanges) {
|
||||
|
||||
bodyRange.getCell(fmt.row - 1, col).format.fill.color = "#ffe699"; // Stronger Yellow
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
targetRange.format.autofitColumns();
|
||||
|
||||
await context.sync();
|
||||
|
||||
|
||||
|
||||
targetSheet.activate();
|
||||
|
||||
return finalOutput.length;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user