Files
numolus/src/data/seed.ts
T
wkhandClaude Sonnet 5 2c54537427 Text-Vergleich, &-Verkettung und benannte Bereiche
- Vergleichsoperatoren (= <> > < >= <=) unterscheiden jetzt Text- von
  Zahlenvergleich statt jede Seite zwangsweise durch toNumber() zu
  schicken (=A1="Nord" gab vorher #VALUE! statt 1); ermöglicht
  Text-Kriterien in selbstgebauten COUNTIF/SUMIF/AVERAGEIF-Funktionen
  (neue README-Sektion "SUMIF/COUNTIF/AVERAGEIF nachbauen").
- Neuer &-Operator (Excel-Präzedenz zwischen Vergleich und +/-) plus
  CONCAT/CONCATENATE als Funktions-Äquivalent.
- Benannte Bereiche: neuer Toolbar-Button "🏷" verwaltet dokumentweite
  Namen für eine feste Zelle/einen festen Bereich (z. B. Umsatz_Q1statt
  Umsatz!B2:B6), unqualifiziert in jeder Top-Level-Formel nutzbar.
  Live gegen die Tabellen-ID aufgelöst (übersteht Tabellen-Umbenennung),
  bewusst nicht innerhalb einer benutzerdefinierten Funktion selbst
  nutzbar (Aufrufer können sie aber als Argument übergeben).
- Nebenbei: COUNT2s Autovervollständigungs-Hint korrigiert (zeigte
  "COUNT(...)" statt "COUNT2(...)").

374 Tests, vue-tsc/eslint/build sauber, Playwright-live-verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 21:59:47 +02:00

146 lines
5.0 KiB
TypeScript

/**
* The pre-filled document ("roter Faden" of the study), in the v2 canvas model:
* one sheet ("Analyse") carrying three table objects and a chart object, plus a
* second empty sheet to try things out.
*
* Note on the brief: the acceptance list mentions "Übersicht!B2 = 6070" as the
* yearly Nord revenue, but the given Nord row is 1200 + 1450 + 1600 + 1720 =
* 5970. The explicit table wins -- the exact number is deterministic and the
* propagation behaviour it demonstrates is unchanged.
*/
import { parseA1 } from '../engine';
import type { ChartObject, DocumentState, TableObject } from '../store/types';
import { defaultTableView } from '../store/types';
let uid = 0;
const nextId = (p: string) => `${p}-seed-${(uid++).toString(36)}`;
function makeTable(
name: string,
x: number,
y: number,
rows: number,
cols: number,
entries: Array<[string, string | number]>,
colWidths: Record<number, number> = {},
viewOverride: Partial<TableObject['view']> = {},
): TableObject {
const cells: Record<string, string> = {};
for (const [a1, value] of entries) {
const { col, row } = parseA1(a1);
cells[`${col},${row}`] = String(value);
}
return {
kind: 'table',
id: nextId('tbl'),
name,
x,
y,
rows,
cols,
colWidths,
cells,
view: { ...defaultTableView(), ...viewOverride },
};
}
export function createSeedDocument(): DocumentState {
const umsatz = makeTable('Umsatz', 40, 40, 20, 8, [
['A1', 'Region'], ['B1', 'Q1'], ['C1', 'Q2'], ['D1', 'Q3'], ['E1', 'Q4'],
['A2', 'Nord'], ['B2', 1200], ['C2', 1450], ['D2', 1600], ['E2', 1720],
['A3', 'Süd'], ['B3', 980], ['C3', 1100], ['D3', 1250], ['E3', 1310],
['A4', 'Ost'], ['B4', 1550], ['C4', 1480], ['D4', 1620], ['E4', 1700],
['A5', 'West'], ['B5', 1340], ['C5', 1390], ['D5', 1500], ['E5', 1580],
['A6', 'Zentral'], ['B6', 1105], ['C6', 1205], ['D6', 1298], ['E6', 1402],
['A8', 'Summe'],
['B8', '=SUM(B2:B6)'], ['C8', '=SUM(C2:C6)'], ['D8', '=SUM(D2:D6)'], ['E8', '=SUM(E2:E6)'],
['A9', 'Mittelwert'],
['B9', '=AVG(B2:B6)'], ['C9', '=AVG(C2:C6)'], ['D9', '=AVG(D2:D6)'], ['E9', '=AVG(E2:E6)'],
['A10', 'Minimum'],
['B10', '=MIN(B2:B6)'], ['C10', '=MIN(C2:C6)'], ['D10', '=MIN(D2:D6)'], ['E10', '=MIN(E2:E6)'],
['A11', 'Maximum'],
['B11', '=MAX(B2:B6)'], ['C11', '=MAX(C2:C6)'], ['D11', '=MAX(D2:D6)'], ['E11', '=MAX(E2:E6)'],
['A12', 'Anzahl'],
['B12', '=COUNT(B2:B6)'], ['C12', '=COUNT(C2:C6)'], ['D12', '=COUNT(D2:D6)'], ['E12', '=COUNT(E2:E6)'],
['G2', 'Jahr Nord'], ['H2', '=SUM(B2:E2)'],
['G3', 'Schnitt/Q'], ['H3', '=SUM(B2:E2)/4'],
['G4', 'Delta Q4-Q1'], ['H4', '=E2-B2'],
], { 0: 92, 6: 96 }, { mode: 'fixed', width: 545, height: 360, frozenRows: 1, frozenCols: 1 });
const kosten = makeTable('Kosten', 40, 700, 12, 8, [
['A1', 'Region'], ['B1', 'Q1'], ['C1', 'Q2'], ['D1', 'Q3'], ['E1', 'Q4'],
['A2', 'Nord'], ['B2', 800], ['C2', 820], ['D2', 900], ['E2', 880],
['A3', 'Süd'], ['B3', 700], ['C3', 690], ['D3', 710], ['E3', 720],
['A4', 'Ost'], ['B4', 900], ['C4', 910], ['D4', 950], ['E4', 980],
['A5', 'West'], ['B5', 760], ['C5', 770], ['D5', 780], ['E5', 790],
['A6', 'Zentral'], ['B6', 640], ['C6', 650], ['D6', 660], ['E6', 670],
], { 0: 92 });
const regions = ['Nord', 'Süd', 'Ost', 'West', 'Zentral'];
const overview: Array<[string, string | number]> = [
['A1', 'Region'], ['B1', 'Umsatz Jahr'], ['C1', 'Kosten Jahr'],
['D1', 'Deckungsbeitrag'], ['E1', 'Marge %'],
];
regions.forEach((region, i) => {
const r = i + 2;
overview.push(
[`A${r}`, region],
[`B${r}`, `=SUM(Umsatz!B${r}:E${r})`],
[`C${r}`, `=SUM(Kosten!B${r}:E${r})`],
[`D${r}`, `=B${r}-C${r}`],
[`E${r}`, `=D${r}/B${r}*100`],
);
});
overview.push(
['A8', 'Gesamt'],
['B8', '=SUM(B2:B6)'], ['C8', '=SUM(C2:C6)'],
['D8', '=SUM(D2:D6)'], ['E8', '=AVG(E2:E6)'],
);
const uebersicht = makeTable('Übersicht', 1000, 40, 12, 6, overview, {
0: 84, 3: 118,
});
const chart: ChartObject = {
kind: 'chart',
id: nextId('cht'),
name: 'Umsatz je Region',
x: 1000,
y: 460,
width: 560,
height: 340,
view: 'bar',
sourceTableId: umsatz.id,
};
const analyse = {
id: nextId('sheet'),
name: 'Analyse',
objects: [umsatz, kosten, uebersicht, chart],
};
// second sheet demos the per-sheet backdrop colour + hidden grid
const blatt2 = {
id: nextId('sheet'),
name: 'Blatt 2',
objects: [],
background: '#f4f1ff',
showGrid: false,
};
return {
version: 2,
sheets: [analyse, blatt2],
activeSheetId: analyse.id,
activeObjectId: umsatz.id,
selection: { tableId: umsatz.id, anchor: { col: 0, row: 0 }, focus: { col: 0, row: 0 } },
locale: 'de-DE',
csvDelimiter: ',',
customFunctions: [],
namedRanges: [
// demos "🏷 Namen": =Umsatz_Q1 anywhere resolves to Umsatz!B2:B6, even
// from a different sheet, without a table qualifier.
{ id: nextId('nr'), name: 'Umsatz_Q1', tableId: umsatz.id, start: { col: 1, row: 1 }, end: { col: 1, row: 5 } },
],
};
}