From 14b01d890f31a7274c8594ffdc07c9135fb73ab8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 15:18:42 +0000 Subject: [PATCH 1/7] Rapport: resourcediagram groeperen op resourcetype in twee lagen (#113, manuvarkey punt 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optie "Groeperen op resourcetype": eerst een band per type — arbeid, ploeg, onderaannemer, materieel, materiaal, in die vaste volgorde (niet op vertaald label, zodat een uitgedeeld vel in elke taal dezelfde blokvolgorde heeft) — daarbinnen de resourcebanden zoals voorheen, de taken op diepte 2. "(geen)" blijft achteraan op diepte 0: taken zonder resource hebben geen type. Zonder de optie zijn de rijen byte-identiek. Bij "elke resource op een nieuwe pagina" krijgt een band direct onder een band geen eigen gedwongen breekpositie, zodat een typekop bij zijn eerste resource blijft in plaats van alleen op een leeg vel te staan. Instelling in reportSettings.resourceGantt (tolerante parser), vinkje in het paneel, sleutel in 14 locales, gidsen nl+en. Regressie: check-reports (volgorde, dieptes, tellingen, unieke sleutels, byte-identiek zonder optie, gelijknamigen over twee typen), check-print-report (breekposities bij een typelaag), settings-check, browserspec (typekop blijft bij eerste resource: nog steeds drie pagina's). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq --- public/docs/en/gids-rapporten-printen.md | 6 +- public/docs/nl/gids-rapporten-printen.md | 6 +- src/components/panels/ReportPanel.tsx | 20 ++++++- src/engine/reports/resourceGantt.ts | 56 +++++++++++++++++-- src/i18n/locales/ar/report.json | 1 + src/i18n/locales/de/report.json | 1 + src/i18n/locales/en/report.json | 1 + src/i18n/locales/es/report.json | 1 + src/i18n/locales/fa/report.json | 1 + src/i18n/locales/fr/report.json | 1 + src/i18n/locales/it/report.json | 1 + src/i18n/locales/ja/report.json | 1 + src/i18n/locales/ko/report.json | 1 + src/i18n/locales/nl/report.json | 1 + src/i18n/locales/pl/report.json | 1 + src/i18n/locales/pt/report.json | 1 + src/i18n/locales/tr/report.json | 1 + src/i18n/locales/zh/report.json | 1 + src/services/print/printPreview.ts | 4 +- src/utils/reportSettings.ts | 5 ++ tests/browser/resource-gantt-report.spec.ts | 5 ++ tests/planning/check-print-report.ts | 12 ++++ .../check-report-resource-gantt-setting.ts | 12 ++-- tests/planning/check-reports.ts | 25 +++++++++ 24 files changed, 150 insertions(+), 15 deletions(-) diff --git a/public/docs/en/gids-rapporten-printen.md b/public/docs/en/gids-rapporten-printen.md index 781dda60..18221506 100644 --- a/public/docs/en/gids-rapporten-printen.md +++ b/public/docs/en/gids-rapporten-printen.md @@ -101,7 +101,11 @@ with the tasks assigned to it underneath, in order of start. That is the "who do overview for the site meeting, or — with the option **Each resource on a new page** — a separate sheet per person to hand out. A task with two resources appears under both bands; summary tasks are left out — an assignment on a summary task (which an import can produce) is not shown here. **Include tasks without a resource** adds a *(none)* band -at the bottom, so the meeting can see at a glance what nobody owns yet. +at the bottom, so the meeting can see at a glance what nobody owns yet. **Group by resource type** +adds a layer above: first a band per type — labour, crew, subcontractor, equipment, material, in +that fixed order — with the resources inside it; useful when a meeting discusses the people first +and the equipment after, or when you only want to print the equipment block. With *Each resource on +a new page* a type band starts a new sheet together with its first resource. The settings of the Gantt print apply here too — critical path, float, bar colours, status line, paper, header repeat — with three exceptions: *Follow view* (this report doesn't take its rows from diff --git a/public/docs/nl/gids-rapporten-printen.md b/public/docs/nl/gids-rapporten-printen.md index 35b4bebe..317de9cf 100644 --- a/public/docs/nl/gids-rapporten-printen.md +++ b/public/docs/nl/gids-rapporten-printen.md @@ -104,7 +104,11 @@ een nieuwe pagina** — een los vel per persoon om uit te delen. Een taak met tw onder beide banden; verzameltaken doen niet mee — een toewijzing op een verzameltaak (die uit een import kan komen) wordt hier niet getoond. Met **Taken zonder resource meenemen** komt er onderaan een band *(geen)* bij, zodat je in het overleg meteen ziet wat -nog niemand heeft. +nog niemand heeft. Met **Groeperen op resourcetype** komt er een laag boven: eerst een band per type +— arbeid, ploeg, onderaannemer, materieel, materiaal, in die vaste volgorde — en daarbinnen de +resources; handig als je in het overleg eerst de mensen en dan het materieel bespreekt, of alleen het +materieelblok wilt afdrukken. Bij *Elke resource op een nieuwe pagina* begint een typeband op een +nieuw vel samen met zijn eerste resource. De instellingen van de Gantt-afdruk gelden hier ook — kritiek pad, speling, balkkleuren, statuslijn, papier, kopherhaling — op drie na: *Volg weergave* (de rijen komen bij dit rapport niet van het diff --git a/src/components/panels/ReportPanel.tsx b/src/components/panels/ReportPanel.tsx index d7689fd6..b2521f22 100644 --- a/src/components/panels/ReportPanel.tsx +++ b/src/components/panels/ReportPanel.tsx @@ -447,12 +447,20 @@ export function ReportPanel() { const isGanttLike = isGanttReportType(reportType); const noneLabel = tTask('structure.none'); // De bandvolgorde volgt de app-taal (nooit de OS-taal van de afdrukker: zelfde vel, zelfde nummering). + // Typelabels voor de optionele typelaag (punt 2): dezelfde sleutels als het resourcepaneel. + const resourceTypeLabels = useMemo(() => ({ + LABOR: tCommon('resource.type.labor'), CREW: tCommon('resource.type.crew'), + SUBCONTRACTOR: tCommon('resource.type.subcontractor'), EQUIPMENT: tCommon('resource.type.equipment'), + MATERIAL: tCommon('resource.type.material'), + }), [tCommon]); const resourceGantt = useMemo(() => (reportType === 'resourceGantt' ? computeResourceGanttRows({ tasks, resources, assignments }, { includeUnassigned: resourceGanttOptions.includeUnassigned, noneLabel, locale: i18n.language, + groupByType: resourceGanttOptions.groupByType, typeLabels: resourceTypeLabels, }) : null), - [reportType, tasks, resources, assignments, noneLabel, resourceGanttOptions.includeUnassigned, i18n.language]); + [reportType, tasks, resources, assignments, noneLabel, resourceGanttOptions.includeUnassigned, + resourceGanttOptions.groupByType, resourceTypeLabels, i18n.language]); // Rijenbron van de Gantt-render: resourcediagram ⇒ de resourcebanden; Gantt-afdruk ⇒ de schermrijen // bij Volg weergave (#54), anders `undefined` = de volledige takenboom (oud gedrag, geen verrassingen). const reportRows = resourceGantt ? resourceGantt.rows : followView ? viewRows : undefined; @@ -1400,6 +1408,16 @@ export function ReportPanel() { /> {t('resourceGantt.includeUnassigned')} + )} diff --git a/src/engine/reports/resourceGantt.ts b/src/engine/reports/resourceGantt.ts index 3ab0814b..8d242295 100644 --- a/src/engine/reports/resourceGantt.ts +++ b/src/engine/reports/resourceGantt.ts @@ -1,5 +1,5 @@ import type { Task } from '@/types/task'; -import type { Resource, ResourceAssignment } from '@/types/resource'; +import type { Resource, ResourceAssignment, ResourceType } from '@/types/resource'; import { encodeBandKey, encodeGroupedTaskRowKey, NONE_RAWKEY, type ViewRow } from '@/engine/view/visibleRows'; import { taskStart } from './reportCommon'; @@ -30,6 +30,12 @@ import { taskStart } from './reportCommon'; * eenduidig anker en zou op een blad per persoon de bladrand af lopen — het paneel zet `showDeps` * voor dit type uit (bevinding 2). * + * Optioneel in TWEE LAGEN (manuvarkey op #113, punt 2): eerst een band per resourceTYPE (mensen + * eerst — arbeid, ploeg, onderaannemer — dan materieel, dan materiaal; een vaste volgorde, niet + * de vertaalde labelvolgorde, zodat een uitgedeeld vel in elke taal dezelfde blokvolgorde heeft), + * daarbinnen de resourcebanden zoals hierboven, en de taken op diepte 2. De "(geen)"-band blijft + * op diepte 0 als laatste: taken zonder resource hebben geen type. + * * Invoer is structureel een `ReportContext`-subset, zoals de rest van `src/engine/reports/`. Puur: * geen React-/store-imports, headless getest in `tests/planning/check-reports.ts`. */ @@ -46,6 +52,11 @@ export interface ResourceGanttRowsOptions { noneLabel: string; /** BCP-47-taal voor de bandvolgorde en de gelijknaamdetectie (de app-taal, `i18n.language`). */ locale: string; + /** Twee lagen: een band per resourcetype, daarbinnen per resource (default uit). */ + groupByType?: boolean; + /** Labels per resourcetype — `t('common:resource.type.')`; een ontbrekend label valt + * terug op de enum-naam. Alleen gelezen bij `groupByType`. */ + typeLabels?: Partial>; } export interface ResourceGanttRowsResult { @@ -66,6 +77,18 @@ export interface ResourceGanttRowsResult { /** Bandsleutel van de "(geen)"-band — dezelfde codering als de schermgroepering. */ const NONE_BAND_KEY = encodeBandKey([NONE_RAWKEY]); +/** + * Bandvolgorde van de typen bij `groupByType`: wie het werk doet eerst, dan waarmee, dan waarvan. + * Bewust vast en niet op vertaald label gesorteerd — zie de moduledoc. Een type dat hier zou + * ontbreken (kan niet met het huidige enum) komt achteraan. + */ +export const RESOURCE_TYPE_BAND_ORDER: readonly ResourceType[] = ['LABOR', 'CREW', 'SUBCONTRACTOR', 'EQUIPMENT', 'MATERIAL']; + +/** Ruwe bandsleutel van een typeband — met prefix, zodat hij nooit botst met een resource-id. */ +function typeRawKey(type: ResourceType): string { + return `type:${type}`; +} + /** Op start (`taskStart`, dezelfde definitie als de tabelrapporten), lege datums achteraan, * gelijke starts in invoervolgorde (stabiel). */ function sortByStart(tasks: Task[]): Task[] { @@ -144,15 +167,38 @@ export function computeResourceGanttRows( .sort((a, b) => collator.compare(a.label, b.label) || a.index - b.index); const rows: ViewRow[] = []; - for (const band of bands) { - const key = encodeBandKey([band.id]); + // Eén resourceband met zijn taakrijen, onder een optioneel typepad (diepte +1). + const pushBand = (band: (typeof bands)[number], path: string[]) => { + const groupPath = [...path, band.id]; + const key = encodeBandKey(groupPath); rows.push({ kind: 'group', rowKey: key, key, label: band.label, count: band.tasks.size, - depth: 0, levelIndex: 0, collapsed: false, + depth: path.length, levelIndex: path.length, collapsed: false, }); for (const task of sortByStart([...band.tasks.values()])) { - rows.push({ kind: 'task', rowKey: encodeGroupedTaskRowKey([band.id], task.id), task, depth: 1, dimmed: false }); + rows.push({ kind: 'task', rowKey: encodeGroupedTaskRowKey(groupPath, task.id), task, depth: path.length + 1, dimmed: false }); + } + }; + if (opts.groupByType) { + const typeOf = new Map(ctx.resources.map(r => [r.id, r.type])); + const rank = (type: ResourceType) => { + const i = RESOURCE_TYPE_BAND_ORDER.indexOf(type); + return i < 0 ? RESOURCE_TYPE_BAND_ORDER.length : i; + }; + // Typen in de vaste volgorde; binnen een type blijft de collator-volgorde van `bands`. + const types = [...new Set(bands.map(b => typeOf.get(b.id) as ResourceType))].sort((a, b) => rank(a) - rank(b)); + for (const type of types) { + const members = bands.filter(b => typeOf.get(b.id) === type); + const raw = typeRawKey(type); + const key = encodeBandKey([raw]); + rows.push({ + kind: 'group', rowKey: key, key, label: opts.typeLabels?.[type] ?? type, + count: members.reduce((n, b) => n + b.tasks.size, 0), depth: 0, levelIndex: 0, collapsed: false, + }); + for (const band of members) pushBand(band, [raw]); } + } else { + for (const band of bands) pushBand(band, []); } const unassigned = sortByStart(leaves.filter(t => !assignedTaskIds.has(t.id))); diff --git a/src/i18n/locales/ar/report.json b/src/i18n/locales/ar/report.json index f092c4dc..8967183c 100644 --- a/src/i18n/locales/ar/report.json +++ b/src/i18n/locales/ar/report.json @@ -146,6 +146,7 @@ "resourceGantt": { "pageBreakPerResource": "كل مورد في صفحة جديدة", "includeUnassigned": "تضمين المهام بدون مورد", + "groupByType": "تجميع حسب نوع المورد", "empty": "لا توجد تعيينات موارد — عيّن الموارد للمهام أولاً.", "resources": "الموارد:", "assignments": "التعيينات:", diff --git a/src/i18n/locales/de/report.json b/src/i18n/locales/de/report.json index 8915a09e..32fb4e1f 100644 --- a/src/i18n/locales/de/report.json +++ b/src/i18n/locales/de/report.json @@ -134,6 +134,7 @@ "resourceGantt": { "pageBreakPerResource": "Jede Ressource auf einer neuen Seite", "includeUnassigned": "Aufgaben ohne Ressource einbeziehen", + "groupByType": "Nach Ressourcentyp gruppieren", "empty": "Keine Ressourcenzuweisungen — weisen Sie zuerst Ressourcen zu Aufgaben zu.", "resources": "Ressourcen:", "assignments": "Zuweisungen:", diff --git a/src/i18n/locales/en/report.json b/src/i18n/locales/en/report.json index b7623ac1..b6e6e95b 100644 --- a/src/i18n/locales/en/report.json +++ b/src/i18n/locales/en/report.json @@ -134,6 +134,7 @@ "resourceGantt": { "pageBreakPerResource": "Each resource on a new page", "includeUnassigned": "Include tasks without a resource", + "groupByType": "Group by resource type", "empty": "No resource assignments — assign resources to tasks first.", "resources": "Resources:", "assignments": "Assignments:", diff --git a/src/i18n/locales/es/report.json b/src/i18n/locales/es/report.json index 348a71c2..f2248703 100644 --- a/src/i18n/locales/es/report.json +++ b/src/i18n/locales/es/report.json @@ -137,6 +137,7 @@ "resourceGantt": { "pageBreakPerResource": "Cada recurso en una página nueva", "includeUnassigned": "Incluir tareas sin recurso", + "groupByType": "Agrupar por tipo de recurso", "empty": "No hay asignaciones de recursos — asigne primero recursos a las tareas.", "resources": "Recursos:", "assignments": "Asignaciones:", diff --git a/src/i18n/locales/fa/report.json b/src/i18n/locales/fa/report.json index 1859b27c..94bec2d6 100644 --- a/src/i18n/locales/fa/report.json +++ b/src/i18n/locales/fa/report.json @@ -135,6 +135,7 @@ "resourceGantt": { "pageBreakPerResource": "هر منبع در صفحه‌ای جدید", "includeUnassigned": "شامل کردن کارهای بدون منبع", + "groupByType": "گروه‌بندی بر اساس نوع منبع", "empty": "هیچ تخصیص منبعی وجود ندارد — ابتدا منابع را به کارها تخصیص دهید.", "resources": "منابع:", "assignments": "تخصیص‌ها:", diff --git a/src/i18n/locales/fr/report.json b/src/i18n/locales/fr/report.json index bb6f6a62..586e3de5 100644 --- a/src/i18n/locales/fr/report.json +++ b/src/i18n/locales/fr/report.json @@ -137,6 +137,7 @@ "resourceGantt": { "pageBreakPerResource": "Chaque ressource sur une nouvelle page", "includeUnassigned": "Inclure les tâches sans ressource", + "groupByType": "Grouper par type de ressource", "empty": "Aucune affectation de ressource — affectez d'abord des ressources aux tâches.", "resources": "Ressources :", "assignments": "Affectations :", diff --git a/src/i18n/locales/it/report.json b/src/i18n/locales/it/report.json index c0283846..0e2c916d 100644 --- a/src/i18n/locales/it/report.json +++ b/src/i18n/locales/it/report.json @@ -137,6 +137,7 @@ "resourceGantt": { "pageBreakPerResource": "Ogni risorsa su una nuova pagina", "includeUnassigned": "Includi le attività senza risorsa", + "groupByType": "Raggruppa per tipo di risorsa", "empty": "Nessuna assegnazione di risorse — assegna prima le risorse alle attività.", "resources": "Risorse:", "assignments": "Assegnazioni:", diff --git a/src/i18n/locales/ja/report.json b/src/i18n/locales/ja/report.json index 9fac187b..76116886 100644 --- a/src/i18n/locales/ja/report.json +++ b/src/i18n/locales/ja/report.json @@ -131,6 +131,7 @@ "resourceGantt": { "pageBreakPerResource": "リソースごとに改ページ", "includeUnassigned": "リソース未割当のタスクを含める", + "groupByType": "リソース種別でグループ化", "empty": "リソースの割当がありません — まずタスクにリソースを割り当ててください。", "resources": "リソース:", "assignments": "割当:", diff --git a/src/i18n/locales/ko/report.json b/src/i18n/locales/ko/report.json index 45118c02..01f4df6a 100644 --- a/src/i18n/locales/ko/report.json +++ b/src/i18n/locales/ko/report.json @@ -131,6 +131,7 @@ "resourceGantt": { "pageBreakPerResource": "자원마다 새 페이지", "includeUnassigned": "자원이 없는 작업 포함", + "groupByType": "자원 유형별로 그룹화", "empty": "자원 배정이 없습니다 — 먼저 작업에 자원을 배정하세요.", "resources": "자원:", "assignments": "배정:", diff --git a/src/i18n/locales/nl/report.json b/src/i18n/locales/nl/report.json index d4d2c231..21415b41 100644 --- a/src/i18n/locales/nl/report.json +++ b/src/i18n/locales/nl/report.json @@ -134,6 +134,7 @@ "resourceGantt": { "pageBreakPerResource": "Elke resource op een nieuwe pagina", "includeUnassigned": "Taken zonder resource meenemen", + "groupByType": "Groeperen op resourcetype", "empty": "Geen resourcetoewijzingen — wijs eerst resources toe aan taken.", "resources": "Resources:", "assignments": "Toewijzingen:", diff --git a/src/i18n/locales/pl/report.json b/src/i18n/locales/pl/report.json index 44f07b15..99b1453c 100644 --- a/src/i18n/locales/pl/report.json +++ b/src/i18n/locales/pl/report.json @@ -140,6 +140,7 @@ "resourceGantt": { "pageBreakPerResource": "Każdy zasób na nowej stronie", "includeUnassigned": "Uwzględnij zadania bez zasobu", + "groupByType": "Grupuj według typu zasobu", "empty": "Brak przypisań zasobów — najpierw przypisz zasoby do zadań.", "resources": "Zasoby:", "assignments": "Przypisania:", diff --git a/src/i18n/locales/pt/report.json b/src/i18n/locales/pt/report.json index b7bd207f..59d35cf3 100644 --- a/src/i18n/locales/pt/report.json +++ b/src/i18n/locales/pt/report.json @@ -137,6 +137,7 @@ "resourceGantt": { "pageBreakPerResource": "Cada recurso numa nova página", "includeUnassigned": "Incluir tarefas sem recurso", + "groupByType": "Agrupar por tipo de recurso", "empty": "Sem atribuições de recursos — atribua primeiro recursos às tarefas.", "resources": "Recursos:", "assignments": "Atribuições:", diff --git a/src/i18n/locales/tr/report.json b/src/i18n/locales/tr/report.json index cf25531a..0543a94a 100644 --- a/src/i18n/locales/tr/report.json +++ b/src/i18n/locales/tr/report.json @@ -134,6 +134,7 @@ "resourceGantt": { "pageBreakPerResource": "Her kaynak yeni bir sayfada", "includeUnassigned": "Kaynaksız görevleri dahil et", + "groupByType": "Kaynak türüne göre grupla", "empty": "Kaynak ataması yok — önce görevlere kaynak atayın.", "resources": "Kaynaklar:", "assignments": "Atamalar:", diff --git a/src/i18n/locales/zh/report.json b/src/i18n/locales/zh/report.json index 7e8c7eae..f5836e1b 100644 --- a/src/i18n/locales/zh/report.json +++ b/src/i18n/locales/zh/report.json @@ -131,6 +131,7 @@ "resourceGantt": { "pageBreakPerResource": "每个资源另起一页", "includeUnassigned": "包含未分配资源的任务", + "groupByType": "按资源类型分组", "empty": "没有资源分配——请先为任务分配资源。", "resources": "资源:", "assignments": "分配:", diff --git a/src/services/print/printPreview.ts b/src/services/print/printPreview.ts index 7c9026e7..bd78414c 100644 --- a/src/services/print/printPreview.ts +++ b/src/services/print/printPreview.ts @@ -1206,7 +1206,9 @@ export function renderReport( // Resourcediagram (issue #113): een gedwongen overgang vóór elke bandrij ná de eerste — de // bovenrand van rij i is de onderrand van rij i-1, dus exact een bestaande breekpositie. const forcedBreakOffsets = options.pageBreakBeforeGroups - ? printRows.flatMap((row, i) => (row.kind === 'group' && i > 0 ? [m.totalHeaderHeight + i * m.rowHeight] : [])) + // Een band direct ónder een band (typelaag, punt 2) blijft bij zijn ouder: anders zou de typekop + // alleen op een verder leeg vel staan. + ? printRows.flatMap((row, i) => (row.kind === 'group' && i > 0 && printRows[i - 1].kind !== 'group' ? [m.totalHeaderHeight + i * m.rowHeight] : [])) : undefined; return { width: canvasWidth, height: canvasHeight, tableWidth: m.tableWidth, headerHeight: m.totalHeaderHeight, diff --git a/src/utils/reportSettings.ts b/src/utils/reportSettings.ts index 3df813e6..42b3f0bf 100644 --- a/src/utils/reportSettings.ts +++ b/src/utils/reportSettings.ts @@ -137,15 +137,19 @@ export type TableReportPeriodKey = (typeof TABLE_REPORT_PERIOD_KEYS)[number]; * elke resource begint op een nieuwe pagina, zodat je per ploeg of medewerker één vel kunt * uitdelen; uit = één doorlopend overlegdocument. `includeUnassigned` neemt de taken zonder * resource als laatste band mee — handig om in een overleg te zien wat nog niemand heeft. + * `groupByType` (manuvarkey, punt 2) zet er een laag boven: eerst een band per resourcetype + * (arbeid, ploeg, onderaannemer, materieel, materiaal), daarbinnen per resource. */ export interface ResourceGanttReportOptions { pageBreakPerResource: boolean; includeUnassigned: boolean; + groupByType: boolean; } export const DEFAULT_RESOURCE_GANTT_OPTIONS: ResourceGanttReportOptions = { pageBreakPerResource: false, includeUnassigned: false, + groupByType: false, }; /** Grenzen van de numerieke opties (de UI en de loader delen ze). */ @@ -330,6 +334,7 @@ function parseResourceGanttOptions(raw: unknown): ResourceGanttReportOptions { return { pageBreakPerResource: parseBoolean(s.pageBreakPerResource) ?? d.pageBreakPerResource, includeUnassigned: parseBoolean(s.includeUnassigned) ?? d.includeUnassigned, + groupByType: parseBoolean(s.groupByType) ?? d.groupByType, }; } diff --git a/tests/browser/resource-gantt-report.spec.ts b/tests/browser/resource-gantt-report.spec.ts index 0a97a555..115f20e6 100644 --- a/tests/browser/resource-gantt-report.spec.ts +++ b/tests/browser/resource-gantt-report.spec.ts @@ -57,6 +57,11 @@ test('resourcediagram: rapporttype rendert per resource, opties sturen samenvatt await expect(pages).toHaveCount(3); await expect(count('unassigned')).toHaveText('1'); + // Typelaag (manuvarkey punt 2): Ploeg A (ploeg) en Kraan (materieel) krijgen elk een typekop, en die + // blijft bij zijn eerste resource op hetzelfde vel — nog steeds drie pagina's, geen leeg vel per type. + await page.locator('[data-ops-report-option="groupByType"]').check(); + await expect(pages).toHaveCount(3); + // De échte export (vector-tak, zie paginateVector) moet dezelfde drie pagina's opleveren als de // preview: dat is het pad dat de gebruiker in handen krijgt, en de enige plek waar de gedwongen // breekposities de PDF in gaan. diff --git a/tests/planning/check-print-report.ts b/tests/planning/check-print-report.ts index 70334fba..dad02619 100644 --- a/tests/planning/check-print-report.ts +++ b/tests/planning/check-print-report.ts @@ -486,6 +486,18 @@ const baseOptions = (over: Partial = {}): PrintOptions => ({ // Eén band ⇒ niets te breken: geen gedwongen posities, dus ook geen lege eerste pagina. const single = measurePrintReport(bandTasks.slice(0, 2), [], cal, 'Eén resource', baseOptions({ rows: bands.slice(0, 3), pageBreakBeforeGroups: true })); ok(single.forcedBreakOffsets === undefined, 'één band ⇒ geen gedwongen posities'); + // Typelaag (manuvarkey punt 2): een band direct ónder een band krijgt géén eigen gedwongen + // positie — de typekop blijft bij zijn eerste resource; de tweede resource onder hetzelfde type + // en de volgende typekop breken wél. + const typed: ViewRow[] = [ + { kind: 'group', rowKey: 'type0', key: 'type0', label: 'Ploeg', count: 4, depth: 0, levelIndex: 0, collapsed: false }, + ...bands.slice(0, 6).map(r => ({ ...r, depth: r.depth + 1 })), + { kind: 'group', rowKey: 'type1', key: 'type1', label: 'Materieel', count: 2, depth: 0, levelIndex: 0, collapsed: false }, + ...bands.slice(6).map(r => ({ ...r, depth: r.depth + 1 })), + ]; + const typedForced = measurePrintReport(bandTasks, [], cal, 'Resourcediagram', baseOptions({ rows: typed, pageBreakBeforeGroups: true })); + ok(JSON.stringify(typedForced.forcedBreakOffsets) === JSON.stringify([typedForced.headerHeight + 4 * rowH, typedForced.headerHeight + 7 * rowH]), + `typelaag: gedwongen posities vóór resource 2 (rij 4) en typeband 2 (rij 7), niet vóór een band direct onder een typekop (got ${JSON.stringify(typedForced.forcedBreakOffsets)})`); // Dezelfde drie banden in de overige pagineermodi (review op #132, bevinding 9): zonder // kopherhaling, in 'actual' (1 pt = 1 px, horizontaal getegeld) en met de tijdlijn over twee // paginabreedtes — steeds drie body-rijen die exact op de bandgrenzen eindigen. diff --git a/tests/planning/check-report-resource-gantt-setting.ts b/tests/planning/check-report-resource-gantt-setting.ts index c81f1c03..832b2e94 100644 --- a/tests/planning/check-report-resource-gantt-setting.ts +++ b/tests/planning/check-report-resource-gantt-setting.ts @@ -14,7 +14,7 @@ const expect = (label: string, got: unknown, want: unknown) => { if (JSON.stringify(got) !== JSON.stringify(want)) failures.push(`${label}: verwacht ${JSON.stringify(want)}, kreeg ${JSON.stringify(got)}`); }; -expect('defaults: blad per resource uit, taken zonder resource uit', DEFAULT_REPORT_SETTINGS.resourceGantt, { pageBreakPerResource: false, includeUnassigned: false }); +expect('defaults: blad per resource uit, taken zonder resource uit, typelaag uit', DEFAULT_REPORT_SETTINGS.resourceGantt, { pageBreakPerResource: false, includeUnassigned: false, groupByType: false }); expect('defaults: één bron', DEFAULT_REPORT_SETTINGS.resourceGantt, DEFAULT_RESOURCE_GANTT_OPTIONS); expect('isGanttReportType: gantt én resourceGantt', [isGanttReportType('gantt'), isGanttReportType('resourceGantt'), isGanttReportType('milestones'), isGanttReportType('resourceAssignments')], [true, true, false, false]); // Review N6: één predicaat voor "dit type tekent geen relaties" — de forcering van showDeps én het @@ -25,10 +25,10 @@ expect('showDeps staat standaard aan (de forcering doet dus echt iets)', DEFAULT // barColorSelection), dus zonder lijnen is het misleidend — verborgen op hetzelfde predicaat als de relaties. expect('reportTypeShowsCriticalToggle: verborgen waar geen relaties getekend worden', [reportTypeShowsCriticalToggle('gantt'), reportTypeShowsCriticalToggle('resourceGantt'), reportTypeShowsCriticalToggle('milestones')], [true, false, true]); -localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: true } })); +localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: true, groupByType: true } })); const loaded = await loadReportSettings(); expect('opgeslagen rapporttype resourceGantt wordt geladen', loaded.reportType, 'resourceGantt'); -expect('opgeslagen opties worden geladen', loaded.resourceGantt, { pageBreakPerResource: true, includeUnassigned: true }); +expect('opgeslagen opties worden geladen', loaded.resourceGantt, { pageBreakPerResource: true, includeUnassigned: true, groupByType: true }); localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'gantt', showLegend: false })); const zonderBlok = await loadReportSettings(); @@ -37,16 +37,16 @@ expect('… en de overige voorkeuren blijven staan', zonderBlok.showLegend, fals localStorage.setItem('ops-reportSettings', JSON.stringify({ resourceGantt: { pageBreakPerResource: 'ja', includeUnassigned: true } })); const rommel = await loadReportSettings(); -expect('rommel in één veld ⇒ alleen dat veld terug op de default', rommel.resourceGantt, { pageBreakPerResource: false, includeUnassigned: true }); +expect('rommel in één veld ⇒ alleen dat veld terug op de default', rommel.resourceGantt, { pageBreakPerResource: false, includeUnassigned: true, groupByType: false }); localStorage.setItem('ops-reportSettings', JSON.stringify({ resourceGantt: 'aan' })); expect('rommel in het hele blok ⇒ defaults', (await loadReportSettings()).resourceGantt, DEFAULT_RESOURCE_GANTT_OPTIONS); localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'resourceDiagram' })); expect('onbekend rapporttype ⇒ default rapporttype', (await loadReportSettings()).reportType, DEFAULT_REPORT_SETTINGS.reportType); -await saveReportSettings({ ...DEFAULT_REPORT_SETTINGS, reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: false } }); +await saveReportSettings({ ...DEFAULT_REPORT_SETTINGS, reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: false, groupByType: true } }); const saved = await loadReportSettings(); expect('rapporttype overleeft een round-trip', saved.reportType, 'resourceGantt'); -expect('opties overleven een round-trip', saved.resourceGantt, { pageBreakPerResource: true, includeUnassigned: false }); +expect('opties overleven een round-trip', saved.resourceGantt, { pageBreakPerResource: true, includeUnassigned: false, groupByType: true }); if (failures.length > 0) { console.log(`XX report-resource-gantt-setting: ${failures.length} afwijking(en)`); diff --git a/tests/planning/check-reports.ts b/tests/planning/check-reports.ts index 04c1a5fd..d1950390 100644 --- a/tests/planning/check-reports.ts +++ b/tests/planning/check-reports.ts @@ -529,6 +529,31 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); eq('resourceGantt: naamloze resource ⇒ band "#2" (positie in de projectlijst)', naamloos.rows.filter(x => x.kind === 'group').map(label), ['#2']); eq('resourceGantt: naamloze resource — A telt als toegewezen', naamloos.counts, { resources: 1, assignments: 1, unassignedTasks: 6 }); + // manuvarkey op #113, punt 2: twee lagen — typeband (vaste volgorde: mensen, dan materieel, dan + // materiaal), daarin de resourcebanden op diepte 1, de taken op diepte 2; "(geen)" blijft achteraan + // op diepte 0 (taken zonder resource hebben geen type). Zonder de optie byte-identiek. + const typeLabels = { LABOR: 'Arbeid', CREW: 'Ploeg', SUBCONTRACTOR: 'Onderaannemer', EQUIPMENT: 'Materieel', MATERIAL: 'Materiaal' }; + const typed = computeResourceGanttRows(base, { ...opts, groupByType: true, typeLabels }); + const typedGroups = typed.rows.filter(x => x.kind === 'group'); + eq('resourceGantt/type: typebanden vóór hun resources, mensen eerst', typedGroups.map(x => `${x.depth}:${label(x)}`), ['0:Ploeg', '1:Ploeg 1', '0:Materieel', '1:Kraan']); + ok('resourceGantt/type: taakrijen op diepte 2', typed.rows.every(x => x.kind !== 'task' || x.depth === 2)); + ok('resourceGantt/type: rijsleutels uniek', new Set(typed.rows.map(x => x.rowKey)).size === typed.rows.length); + eq('resourceGantt/type: typeband telt de taakrijen eronder', typedGroups.filter(x => x.depth === 0).map(x => x.count), [3, 1]); + eq('resourceGantt/type: tellingen ongewijzigd', typed.counts, r.counts); + eq('resourceGantt/type: dezelfde taken onder Ploeg 1', [...bandTasks(typed.rows, 'Ploeg 1')].sort(), [A, B, D].sort()); + const typedNone = computeResourceGanttRows(base, { ...opts, groupByType: true, typeLabels, includeUnassigned: true }); + const typedNoneGroups = typedNone.rows.filter(x => x.kind === 'group'); + const lastGroup = typedNoneGroups[typedNoneGroups.length - 1]; + ok('resourceGantt/type: "(geen)" blijft als laatste band op diepte 0', label(lastGroup) === '(geen)' && lastGroup.depth === 0); + ok('resourceGantt/type: zonder de optie byte-identiek', JSON.stringify(computeResourceGanttRows(base, { ...opts, typeLabels }).rows) === JSON.stringify(r.rows)); + eq('resourceGantt/type: ontbrekend label ⇒ enum-naam', computeResourceGanttRows(base, { ...opts, groupByType: true }).rows.filter(x => x.kind === 'group' && x.depth === 0).map(label), ['CREW', 'EQUIPMENT']); + // Gelijknamigen binnen één type houden hun volgnummer; de nummering loopt over de hele lijst. + const typedTwins = computeResourceGanttRows({ + tasks: ctx.tasks, resources: [jan1, { ...jan2, type: 'LABOR' }], + assignments: [asg('a1', A, jan1.id), asg('a2', B, jan2.id)], + }, { ...opts, groupByType: true, typeLabels }); + eq('resourceGantt/type: gelijknamigen over twee typen houden hun volgnummer', typedTwins.rows.filter(x => x.kind === 'group').map(x => `${x.depth}:${label(x)}`), ['0:Arbeid', '1:Jan #2', '0:Materieel', '1:Jan #1']); + // Twee toewijzingen van dezelfde resource op één taak zijn één rij; een toewijzing aan een // onbekende resource telt niet (die taak is dan "zonder resource", zoals op het scherm). const dubbel = computeResourceGanttRows({ From 27c984baf8f4449cadc34a015f48515d9c85280e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 15:29:48 +0000 Subject: [PATCH 2/7] Rapport: rapportageperiode als tijdvenster op het resourcediagram (#113, manuvarkey punt 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Het resourcediagram krijgt de gedeelde rapportageperiode (issue #120, `ReportingPeriodField`/`resolveReportingPeriod`) als eigen optie: alleen bladtaken die het venster raken (start ≤ tot én einde ≥ van, dagniveau, dezelfde overlapregel als de tabelrapporten) doen mee, de tellingen volgen die gefilterde set en `counts.outsidePeriod` telt het weggelatene; het paneel toont dat en geeft een eigen lege-staat-tekst. Default *Hele project* = geen venster, byte-identiek aan vóór deze optie. De render kent nu `PrintOptions.timeWindow`: de tijdas loopt exact van `from` t/m `to` zonder de marge van 7/14 dagen, en omdat `Draw2D` geen clip kent wordt de geometrie geklemd op het chartgebied — balken (ook gesplitste), voortgang, speling, mijlpalen, samenvattingshaken (zonder haakje op een afgekapt uiteinde) en baseline; een balk of ruit die helemaal buiten het venster valt tekent niets, ook geen label. Zonder venster is de render byte-identiek. Relatiepijlen worden niet geklemd: het venster wordt alleen aangeboden op het resourcediagram, dat er geen tekent. Instelling via dezelfde tolerante periodeparser als de tabelrapporten, twee sleutels in 14 locales, gidsen nl+en (resourcediagram- en periodesectie). Regressie: check-reports (venster van één dag, overlapregel, outsidePeriod, leeg venster, alles-omvattend venster identiek), check-print-report (asbreedte = vensterdagen × zoom, balk geklemd op de chartrand, geen balk/label buiten het venster, tabelrij blijft, byte-identiek), settings-check (onbekende preset en omgekeerd bereik ⇒ Hele project, round-trip), browserspec (eigen bereik ⇒ één resource, tellingen incl. buiten de periode, terug naar Hele project herstelt alles). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq --- public/docs/en/gids-rapporten-printen.md | 10 +- public/docs/nl/gids-rapporten-printen.md | 9 +- src/components/panels/ReportPanel.tsx | 34 +++- .../panels/reports/ReportingPeriodField.tsx | 6 +- src/engine/reports/resourceGantt.ts | 27 ++- src/i18n/locales/ar/report.json | 2 + src/i18n/locales/de/report.json | 2 + src/i18n/locales/en/report.json | 2 + src/i18n/locales/es/report.json | 2 + src/i18n/locales/fa/report.json | 2 + src/i18n/locales/fr/report.json | 2 + src/i18n/locales/it/report.json | 2 + src/i18n/locales/ja/report.json | 2 + src/i18n/locales/ko/report.json | 2 + src/i18n/locales/nl/report.json | 2 + src/i18n/locales/pl/report.json | 2 + src/i18n/locales/pt/report.json | 2 + src/i18n/locales/tr/report.json | 2 + src/i18n/locales/zh/report.json | 2 + src/services/print/printPreview.ts | 163 +++++++++++------- src/utils/reportSettings.ts | 7 +- tests/browser/resource-gantt-report.spec.ts | 20 +++ tests/planning/check-print-report.ts | 26 +++ .../check-report-resource-gantt-setting.ts | 17 +- tests/planning/check-reports.ts | 31 +++- 25 files changed, 293 insertions(+), 85 deletions(-) diff --git a/public/docs/en/gids-rapporten-printen.md b/public/docs/en/gids-rapporten-printen.md index 18221506..fe3c7bcd 100644 --- a/public/docs/en/gids-rapporten-printen.md +++ b/public/docs/en/gids-rapporten-printen.md @@ -105,7 +105,13 @@ at the bottom, so the meeting can see at a glance what nobody owns yet. **Group adds a layer above: first a band per type — labour, crew, subcontractor, equipment, material, in that fixed order — with the resources inside it; useful when a meeting discusses the people first and the equipment after, or when you only want to print the equipment block. With *Each resource on -a new page* a type band starts a new sheet together with its first resource. +a new page* a type band starts a new sheet together with its first resource. **Reporting period** — +the same control as on the table reports, see further down — limits the diagram to a time window: +only tasks that touch the window take part, the time axis runs exactly from the start to the end +date, and a bar that continues beyond it is cut off at the edge. That lets a meeting put the +look-ahead report and the resource diagram side by side over exactly the same weeks. *Project +duration* (the default) is the usual behaviour; with a window the summary block also counts how +many tasks fall outside it. The settings of the Gantt print apply here too — critical path, float, bar colours, status line, paper, header repeat — with three exceptions: *Follow view* (this report doesn't take its rows from @@ -164,7 +170,7 @@ a few conventions: Four reports work on a time window: look-ahead, progress, resource loading and resource assignments. They share one *Reporting period* control in the report options, with its own -remembered setting per report: +remembered setting per report (the resource diagram above offers the same control): - **Next / last week, 2, 4, 6, 8 or 12 weeks** and **next / last month** — counted from the project's status date (or today if none is set). A preset is inclusive on both ends: *next 4 diff --git a/public/docs/nl/gids-rapporten-printen.md b/public/docs/nl/gids-rapporten-printen.md index 317de9cf..702b5074 100644 --- a/public/docs/nl/gids-rapporten-printen.md +++ b/public/docs/nl/gids-rapporten-printen.md @@ -108,7 +108,12 @@ nog niemand heeft. Met **Groeperen op resourcetype** komt er een laag boven: eer — arbeid, ploeg, onderaannemer, materieel, materiaal, in die vaste volgorde — en daarbinnen de resources; handig als je in het overleg eerst de mensen en dan het materieel bespreekt, of alleen het materieelblok wilt afdrukken. Bij *Elke resource op een nieuwe pagina* begint een typeband op een -nieuw vel samen met zijn eerste resource. +nieuw vel samen met zijn eerste resource. Met **Rapportageperiode** — dezelfde keuze als bij de +tabelrapporten, zie verderop — beperk je het diagram tot een tijdvenster: alleen taken die het +venster raken doen mee, de tijdas loopt precies van de begin- tot de einddatum, en een balk die +verder doorloopt wordt aan de rand afgekapt. Zo leg je in dezelfde vergadering het look-ahead-rapport +en het resourcediagram over exact dezelfde weken naast elkaar. *Hele project* (standaard) is het +gewone gedrag; bij een venster telt het overzichtsblok ook hoeveel taken erbuiten vallen. De instellingen van de Gantt-afdruk gelden hier ook — kritiek pad, speling, balkkleuren, statuslijn, papier, kopherhaling — op drie na: *Volg weergave* (de rijen komen bij dit rapport niet van het @@ -166,7 +171,7 @@ delen een paar afspraken: Vier rapporten werken op een tijdvenster: look-ahead, voortgang, resourcebelasting en resourcetoewijzingen. Ze delen één *Rapportageperiode*-keuze in de rapportopties, met een eigen -onthouden instelling per rapport: +onthouden instelling per rapport (het resourcediagram hierboven kent dezelfde keuze): - **Volgende / afgelopen week, 2, 4, 6, 8 of 12 weken** en **volgende / afgelopen maand** — gerekend vanaf de statusdatum van het project (of vandaag als die ontbreekt). Een preset is inclusief aan diff --git a/src/components/panels/ReportPanel.tsx b/src/components/panels/ReportPanel.tsx index b2521f22..93b51c83 100644 --- a/src/components/panels/ReportPanel.tsx +++ b/src/components/panels/ReportPanel.tsx @@ -27,6 +27,7 @@ import { import { computeResourceGanttRows } from '@/engine/reports'; import type { ViewRow } from '@/engine/view/visibleRows'; import { TableReportView } from './reports/TableReportView'; +import { ReportingPeriodField, useResolvedPeriod } from './reports/ReportingPeriodField'; import { TableReportOptionsBlock } from './reports/TableReportOptionsBlock'; import { useTableReportSpec } from './reports/useTableReportSpec'; import { toPdfSpec } from './reports/tableReportSpec'; @@ -453,14 +454,21 @@ export function ReportPanel() { SUBCONTRACTOR: tCommon('resource.type.subcontractor'), EQUIPMENT: tCommon('resource.type.equipment'), MATERIAL: tCommon('resource.type.material'), }), [tCommon]); + // Rapportageperiode als tijdvenster (punt 3): dezelfde oplossing als het control toont; bij + // *Hele project* geen venster, zodat het rapport byte-identiek blijft aan vóór deze optie. + const resourceGanttPeriod = useResolvedPeriod(resourceGanttOptions.period); + const resourceGanttWindow = reportType === 'resourceGantt' && resourceGanttOptions.period.preset !== 'project' + ? resourceGanttPeriod + : undefined; const resourceGantt = useMemo(() => (reportType === 'resourceGantt' ? computeResourceGanttRows({ tasks, resources, assignments }, { includeUnassigned: resourceGanttOptions.includeUnassigned, noneLabel, locale: i18n.language, groupByType: resourceGanttOptions.groupByType, typeLabels: resourceTypeLabels, + window: resourceGanttWindow, }) : null), [reportType, tasks, resources, assignments, noneLabel, resourceGanttOptions.includeUnassigned, - resourceGanttOptions.groupByType, resourceTypeLabels, i18n.language]); + resourceGanttOptions.groupByType, resourceTypeLabels, resourceGanttWindow, i18n.language]); // Rijenbron van de Gantt-render: resourcediagram ⇒ de resourcebanden; Gantt-afdruk ⇒ de schermrijen // bij Volg weergave (#54), anders `undefined` = de volledige takenboom (oud gedrag, geen verrassingen). const reportRows = resourceGantt ? resourceGantt.rows : followView ? viewRows : undefined; @@ -544,7 +552,11 @@ export function ReportPanel() { labels: { // Resourcediagram zonder één toewijzing: zeg wat er ontbreekt, niet "geen taken" — tenzij er // écht geen taken zijn, dan is "wijs resources toe" het verkeerde advies. - noTasks: reportType === 'resourceGantt' && tasks.length > 0 ? t('resourceGantt.empty') : t('noTasks'), + noTasks: reportType === 'resourceGantt' && tasks.length > 0 + ? (resourceGantt && resourceGantt.rows.length === 0 && resourceGantt.counts.outsidePeriod > 0 + ? t('resourceGantt.emptyPeriod') + : t('resourceGantt.empty')) + : t('noTasks'), printed: t('printed'), legend: { criticalPath: t('legend.criticalPath'), @@ -602,6 +614,8 @@ export function ReportPanel() { rows: reportRows, // Issue #113 "een blad per persoon": gedwongen paginaovergang vóór elke resourceband. pageBreakBeforeGroups: reportType === 'resourceGantt' && resourceGanttOptions.pageBreakPerResource, + // Punt 3: de tijdas op de rapportageperiode (alleen resourcediagram, alleen buiten *Hele project*). + timeWindow: resourceGanttWindow, barColorsLegendLabels: { criticalOutline: t('legend.criticalOutline', { defaultValue: 'Kritiek pad (rand)' }), categoriesMore: (n: number) => t('legend.categoriesMore', { count: n }), @@ -611,7 +625,8 @@ export function ReportPanel() { project.endDate, project.author, dateNotation, weekStartDay, reportCompressNonWorkdays, timelineColumns, reportFontScale, cpmResult, barColorSelection, fieldCtx.activityCodeTypes, fieldCtx.customFieldDefs, reportTaskTypeLabels, tTask, statusLine, statusDate, resources, - assignments, baselineOverlay, reportRows, reportType, resourceGanttOptions.pageBreakPerResource, tasks.length]); + assignments, baselineOverlay, reportRows, reportType, resourceGanttOptions.pageBreakPerResource, tasks.length, + resourceGantt, resourceGanttWindow]); // `options` bevat afgeleide catalogus-/vertaalobjecten die bij een lokale preview-state-update // opnieuw kunnen worden aangemaakt zonder dat hun inhoud wijzigde. De rastertaak gebruikt deze // inhoudssignatuur als effectgrens: anders start `setPreviewPages` zelf opnieuw pagina 0 en 1. @@ -1195,6 +1210,12 @@ export function ReportPanel() { {resourceGantt.counts.assignments} {t('resourceGantt.unassigned')} {resourceGantt.counts.unassignedTasks} + {resourceGanttWindow && ( + <> + {t('resourceGantt.outsidePeriod')} + {resourceGantt.counts.outsidePeriod} + + )} ) : reportType === 'gantt' ? ( <> @@ -1418,6 +1439,13 @@ export function ReportPanel() { /> {t('resourceGantt.groupByType')} + {/* Punt 3: de gedeelde rapportageperiode (issue #120) als tijdvenster van dit rapport. */} + patchResourceGanttOptions({ period: next })} + dataKey="resourceGanttPeriod" + /> )} diff --git a/src/components/panels/reports/ReportingPeriodField.tsx b/src/components/panels/reports/ReportingPeriodField.tsx index 161ee6bb..ec86550a 100644 --- a/src/components/panels/reports/ReportingPeriodField.tsx +++ b/src/components/panels/reports/ReportingPeriodField.tsx @@ -39,8 +39,10 @@ interface Props { dataKey: string; } -/** De referentiedag en projectspanne uit de live store — dezelfde bron als `useReportContext`. */ -function useResolvedPeriod(period: ReportingPeriod): ResolvedPeriod { +/** De referentiedag en projectspanne uit de live store — dezelfde bron als `useReportContext`. + * Ook gebruikt door `ReportPanel` voor het tijdvenster van het resourcediagram, zodat de rijen en + * de tijdas exact het venster nemen dat dit control toont. */ +export function useResolvedPeriod(period: ReportingPeriod): ResolvedPeriod { const tasks = useAppStore(s => s.tasks); const statusDate = useAppStore(s => s.project.statusDate); const today = formatDate(new Date()); diff --git a/src/engine/reports/resourceGantt.ts b/src/engine/reports/resourceGantt.ts index 8d242295..91883031 100644 --- a/src/engine/reports/resourceGantt.ts +++ b/src/engine/reports/resourceGantt.ts @@ -1,7 +1,8 @@ import type { Task } from '@/types/task'; import type { Resource, ResourceAssignment, ResourceType } from '@/types/resource'; import { encodeBandKey, encodeGroupedTaskRowKey, NONE_RAWKEY, type ViewRow } from '@/engine/view/visibleRows'; -import { taskStart } from './reportCommon'; +import { dayOf, taskFinish, taskStart } from './reportCommon'; +import type { ResolvedPeriod } from './reportingPeriod'; /** * Resourcediagram (issue #113, gfayat): "wie doet wat, en wanneer" als GRAFISCH rapport — de @@ -36,6 +37,12 @@ import { taskStart } from './reportCommon'; * daarbinnen de resourcebanden zoals hierboven, en de taken op diepte 2. De "(geen)"-band blijft * op diepte 0 als laatste: taken zonder resource hebben geen type. * + * Optioneel binnen een TIJDVENSTER (punt 3): met `window` (de opgeloste rapportageperiode, issue + * #120) doen alleen bladtaken mee die het venster raken — start ≤ tot én einde ≥ van, op dagniveau, + * dezelfde overlapregel als de tabelrapporten; een taak zonder datums valt erbuiten. Alle + * tellingen volgen die gefilterde set; `counts.outsidePeriod` telt wat er is weggelaten, zodat de + * UI een lege uitkomst kan verklaren. De render krijgt hetzelfde venster als `timeWindow`. + * * Invoer is structureel een `ReportContext`-subset, zoals de rest van `src/engine/reports/`. Puur: * geen React-/store-imports, headless getest in `tests/planning/check-reports.ts`. */ @@ -57,6 +64,8 @@ export interface ResourceGanttRowsOptions { /** Labels per resourcetype — `t('common:resource.type.')`; een ontbrekend label valt * terug op de enum-naam. Alleen gelezen bij `groupByType`. */ typeLabels?: Partial>; + /** Tijdvenster (opgeloste rapportageperiode, ISO-dagen inclusief). Afwezig = hele project. */ + window?: ResolvedPeriod; } export interface ResourceGanttRowsResult { @@ -71,6 +80,8 @@ export interface ResourceGanttRowsResult { /** Bladtaken zonder resource — ook geteld wanneer ze niet in de rijen staan. Telt, anders dan * het tabelrapport Resourcetoewijzingen, óók mijlpalen en hammocks: dit rapport tekent ze. */ unassignedTasks: number; + /** Bladtaken die door het tijdvenster zijn weggelaten (0 zonder venster). */ + outsidePeriod: number; }; } @@ -138,7 +149,14 @@ export function computeResourceGanttRows( ctx: ResourceGanttInput, opts: ResourceGanttRowsOptions, ): ResourceGanttRowsResult { - const leaves = ctx.tasks.filter(t => t.childIds.length === 0); + const allLeaves = ctx.tasks.filter(t => t.childIds.length === 0); + const inWindow = (t: Task): boolean => { + if (!opts.window) return true; + const s = dayOf(taskStart(t)); + const f = dayOf(taskFinish(t)); + return s !== '' && f !== '' && s <= opts.window.to && f >= opts.window.from; + }; + const leaves = allLeaves.filter(inWindow); const leafById = new Map(leaves.map(t => [t.id, t])); const resourceIds = new Set(ctx.resources.map(r => r.id)); @@ -212,5 +230,8 @@ export function computeResourceGanttRows( } } - return { rows, counts: { resources: bands.length, assignments, unassignedTasks: unassigned.length } }; + return { + rows, + counts: { resources: bands.length, assignments, unassignedTasks: unassigned.length, outsidePeriod: allLeaves.length - leaves.length }, + }; } diff --git a/src/i18n/locales/ar/report.json b/src/i18n/locales/ar/report.json index 8967183c..82d87ac3 100644 --- a/src/i18n/locales/ar/report.json +++ b/src/i18n/locales/ar/report.json @@ -147,6 +147,8 @@ "pageBreakPerResource": "كل مورد في صفحة جديدة", "includeUnassigned": "تضمين المهام بدون مورد", "groupByType": "تجميع حسب نوع المورد", + "emptyPeriod": "لا توجد مهام في فترة التقرير — اختر فترة أخرى أو المشروع بأكمله.", + "outsidePeriod": "خارج الفترة:", "empty": "لا توجد تعيينات موارد — عيّن الموارد للمهام أولاً.", "resources": "الموارد:", "assignments": "التعيينات:", diff --git a/src/i18n/locales/de/report.json b/src/i18n/locales/de/report.json index 32fb4e1f..f70003d3 100644 --- a/src/i18n/locales/de/report.json +++ b/src/i18n/locales/de/report.json @@ -135,6 +135,8 @@ "pageBreakPerResource": "Jede Ressource auf einer neuen Seite", "includeUnassigned": "Aufgaben ohne Ressource einbeziehen", "groupByType": "Nach Ressourcentyp gruppieren", + "emptyPeriod": "Keine Vorgänge im Berichtszeitraum — wählen Sie einen anderen Zeitraum oder Gesamtes Projekt.", + "outsidePeriod": "Außerhalb des Zeitraums:", "empty": "Keine Ressourcenzuweisungen — weisen Sie zuerst Ressourcen zu Aufgaben zu.", "resources": "Ressourcen:", "assignments": "Zuweisungen:", diff --git a/src/i18n/locales/en/report.json b/src/i18n/locales/en/report.json index b6e6e95b..a655e8e7 100644 --- a/src/i18n/locales/en/report.json +++ b/src/i18n/locales/en/report.json @@ -135,6 +135,8 @@ "pageBreakPerResource": "Each resource on a new page", "includeUnassigned": "Include tasks without a resource", "groupByType": "Group by resource type", + "emptyPeriod": "No tasks in the reporting period — choose another period or Whole project.", + "outsidePeriod": "Outside the period:", "empty": "No resource assignments — assign resources to tasks first.", "resources": "Resources:", "assignments": "Assignments:", diff --git a/src/i18n/locales/es/report.json b/src/i18n/locales/es/report.json index f2248703..c77ed1de 100644 --- a/src/i18n/locales/es/report.json +++ b/src/i18n/locales/es/report.json @@ -138,6 +138,8 @@ "pageBreakPerResource": "Cada recurso en una página nueva", "includeUnassigned": "Incluir tareas sin recurso", "groupByType": "Agrupar por tipo de recurso", + "emptyPeriod": "No hay tareas en el período del informe — elija otro período o Proyecto completo.", + "outsidePeriod": "Fuera del período:", "empty": "No hay asignaciones de recursos — asigne primero recursos a las tareas.", "resources": "Recursos:", "assignments": "Asignaciones:", diff --git a/src/i18n/locales/fa/report.json b/src/i18n/locales/fa/report.json index 94bec2d6..61e067f3 100644 --- a/src/i18n/locales/fa/report.json +++ b/src/i18n/locales/fa/report.json @@ -136,6 +136,8 @@ "pageBreakPerResource": "هر منبع در صفحه‌ای جدید", "includeUnassigned": "شامل کردن کارهای بدون منبع", "groupByType": "گروه‌بندی بر اساس نوع منبع", + "emptyPeriod": "هیچ کاری در دوره گزارش وجود ندارد — دوره دیگری یا کل پروژه را انتخاب کنید.", + "outsidePeriod": "خارج از دوره:", "empty": "هیچ تخصیص منبعی وجود ندارد — ابتدا منابع را به کارها تخصیص دهید.", "resources": "منابع:", "assignments": "تخصیص‌ها:", diff --git a/src/i18n/locales/fr/report.json b/src/i18n/locales/fr/report.json index 586e3de5..40bdc1ac 100644 --- a/src/i18n/locales/fr/report.json +++ b/src/i18n/locales/fr/report.json @@ -138,6 +138,8 @@ "pageBreakPerResource": "Chaque ressource sur une nouvelle page", "includeUnassigned": "Inclure les tâches sans ressource", "groupByType": "Grouper par type de ressource", + "emptyPeriod": "Aucune tâche dans la période de rapport — choisissez une autre période ou Projet entier.", + "outsidePeriod": "Hors période :", "empty": "Aucune affectation de ressource — affectez d'abord des ressources aux tâches.", "resources": "Ressources :", "assignments": "Affectations :", diff --git a/src/i18n/locales/it/report.json b/src/i18n/locales/it/report.json index 0e2c916d..b1d12837 100644 --- a/src/i18n/locales/it/report.json +++ b/src/i18n/locales/it/report.json @@ -138,6 +138,8 @@ "pageBreakPerResource": "Ogni risorsa su una nuova pagina", "includeUnassigned": "Includi le attività senza risorsa", "groupByType": "Raggruppa per tipo di risorsa", + "emptyPeriod": "Nessuna attività nel periodo del report — scegli un altro periodo o Intero progetto.", + "outsidePeriod": "Fuori dal periodo:", "empty": "Nessuna assegnazione di risorse — assegna prima le risorse alle attività.", "resources": "Risorse:", "assignments": "Assegnazioni:", diff --git a/src/i18n/locales/ja/report.json b/src/i18n/locales/ja/report.json index 76116886..b24fbb90 100644 --- a/src/i18n/locales/ja/report.json +++ b/src/i18n/locales/ja/report.json @@ -132,6 +132,8 @@ "pageBreakPerResource": "リソースごとに改ページ", "includeUnassigned": "リソース未割当のタスクを含める", "groupByType": "リソース種別でグループ化", + "emptyPeriod": "レポート期間内にタスクがありません — 別の期間またはプロジェクト全体を選択してください。", + "outsidePeriod": "期間外:", "empty": "リソースの割当がありません — まずタスクにリソースを割り当ててください。", "resources": "リソース:", "assignments": "割当:", diff --git a/src/i18n/locales/ko/report.json b/src/i18n/locales/ko/report.json index 01f4df6a..0c2de207 100644 --- a/src/i18n/locales/ko/report.json +++ b/src/i18n/locales/ko/report.json @@ -132,6 +132,8 @@ "pageBreakPerResource": "자원마다 새 페이지", "includeUnassigned": "자원이 없는 작업 포함", "groupByType": "자원 유형별로 그룹화", + "emptyPeriod": "보고 기간에 작업이 없습니다 — 다른 기간 또는 전체 프로젝트를 선택하세요.", + "outsidePeriod": "기간 외:", "empty": "자원 배정이 없습니다 — 먼저 작업에 자원을 배정하세요.", "resources": "자원:", "assignments": "배정:", diff --git a/src/i18n/locales/nl/report.json b/src/i18n/locales/nl/report.json index 21415b41..2aeb6529 100644 --- a/src/i18n/locales/nl/report.json +++ b/src/i18n/locales/nl/report.json @@ -135,6 +135,8 @@ "pageBreakPerResource": "Elke resource op een nieuwe pagina", "includeUnassigned": "Taken zonder resource meenemen", "groupByType": "Groeperen op resourcetype", + "emptyPeriod": "Geen taken in de rapportageperiode — kies een andere periode of Hele project.", + "outsidePeriod": "Buiten de periode:", "empty": "Geen resourcetoewijzingen — wijs eerst resources toe aan taken.", "resources": "Resources:", "assignments": "Toewijzingen:", diff --git a/src/i18n/locales/pl/report.json b/src/i18n/locales/pl/report.json index 99b1453c..96248c08 100644 --- a/src/i18n/locales/pl/report.json +++ b/src/i18n/locales/pl/report.json @@ -141,6 +141,8 @@ "pageBreakPerResource": "Każdy zasób na nowej stronie", "includeUnassigned": "Uwzględnij zadania bez zasobu", "groupByType": "Grupuj według typu zasobu", + "emptyPeriod": "Brak zadań w okresie raportu — wybierz inny okres lub Cały projekt.", + "outsidePeriod": "Poza okresem:", "empty": "Brak przypisań zasobów — najpierw przypisz zasoby do zadań.", "resources": "Zasoby:", "assignments": "Przypisania:", diff --git a/src/i18n/locales/pt/report.json b/src/i18n/locales/pt/report.json index 59d35cf3..c9291aee 100644 --- a/src/i18n/locales/pt/report.json +++ b/src/i18n/locales/pt/report.json @@ -138,6 +138,8 @@ "pageBreakPerResource": "Cada recurso numa nova página", "includeUnassigned": "Incluir tarefas sem recurso", "groupByType": "Agrupar por tipo de recurso", + "emptyPeriod": "Nenhuma tarefa no período do relatório — escolha outro período ou Projeto inteiro.", + "outsidePeriod": "Fora do período:", "empty": "Sem atribuições de recursos — atribua primeiro recursos às tarefas.", "resources": "Recursos:", "assignments": "Atribuições:", diff --git a/src/i18n/locales/tr/report.json b/src/i18n/locales/tr/report.json index 0543a94a..c0e398a9 100644 --- a/src/i18n/locales/tr/report.json +++ b/src/i18n/locales/tr/report.json @@ -135,6 +135,8 @@ "pageBreakPerResource": "Her kaynak yeni bir sayfada", "includeUnassigned": "Kaynaksız görevleri dahil et", "groupByType": "Kaynak türüne göre grupla", + "emptyPeriod": "Rapor döneminde görev yok — başka bir dönem veya Tüm proje seçin.", + "outsidePeriod": "Dönem dışında:", "empty": "Kaynak ataması yok — önce görevlere kaynak atayın.", "resources": "Kaynaklar:", "assignments": "Atamalar:", diff --git a/src/i18n/locales/zh/report.json b/src/i18n/locales/zh/report.json index f5836e1b..12903c53 100644 --- a/src/i18n/locales/zh/report.json +++ b/src/i18n/locales/zh/report.json @@ -132,6 +132,8 @@ "pageBreakPerResource": "每个资源另起一页", "includeUnassigned": "包含未分配资源的任务", "groupByType": "按资源类型分组", + "emptyPeriod": "报告期间内没有任务 — 请选择其他期间或整个项目。", + "outsidePeriod": "期间之外:", "empty": "没有资源分配——请先为任务分配资源。", "resources": "资源:", "assignments": "分配:", diff --git a/src/services/print/printPreview.ts b/src/services/print/printPreview.ts index bd78414c..5fe22355 100644 --- a/src/services/print/printPreview.ts +++ b/src/services/print/printPreview.ts @@ -361,6 +361,16 @@ export interface PrintOptions { * `timelineColumns` moet krijgen. Default 1 = oud gedrag (alles op één paginabreedte). */ timelineColumns?: number; + /** + * OPTIONEEL — rapportageperiode als TIJDVENSTER (manuvarkey op #113, punt 3; resourcediagram): de + * tijdas loopt exact van `from` t/m `to` (ISO-dagen, inclusief) zonder de gebruikelijke marge van + * 7/14 dagen, en balken, mijlpalen, speling, voortgang en baseline worden op de chartrand + * afgekapt — `Draw2D` kent geen clip, dus de geometrie zelf wordt geklemd. Welke rijen in het + * venster horen beslist de rijenbron (`computeResourceGanttRows`), niet de render; een rij die + * er toch buiten valt tekent gewoon geen balk. Relatiepijlen worden niet geklemd: het venster + * wordt alleen aangeboden op het resourcediagram, dat er geen tekent. Afwezig ⇒ byte-identiek. + */ + timeWindow?: { from: string; to: string }; /** * Lettergrootte van het GEGENEREERDE RAPPORT als percentage (issue #25 punt 4). 100 (of * ontbrekend) = het oude gedrag, byte-identiek. Werkt bewust RELATIEF: tekst, rijhoogtes, @@ -663,9 +673,15 @@ export function renderReport( } } - // Add padding days - minDate = addCalendarDays(minDate, -7); - maxDate = addCalendarDays(maxDate, 14); + if (options.timeWindow) { + // Tijdvenster: de as is precies het venster (einde exclusief, dus `to` + 1), zonder marge. + minDate = parseDate(options.timeWindow.from); + maxDate = addCalendarDays(parseDate(options.timeWindow.to), 1); + } else { + // Add padding days + minDate = addCalendarDays(minDate, -7); + maxDate = addCalendarDays(maxDate, 14); + } const calendarDays = diffCalendarDays(minDate, maxDate); @@ -711,6 +727,10 @@ export function renderReport( const chartWidth = timelineDays * zoom; const canvasWidth = m.tableWidth + chartWidth; + // Tijdvenster: chart-x klemmen op het chartgebied (zie `PrintOptions.timeWindow`). Zonder venster + // is dit de identiteit, zodat de oude render byte-identiek blijft. + const windowed = !!options.timeWindow; + const clampX = (x: number) => (windowed ? Math.min(canvasWidth, Math.max(m.tableWidth, x)) : x); // Rij-aantal voor de hoogte: ALLE printrijen (taken + groepsbanden) — de banden zijn volle rijen. const canvasHeight = m.totalHeaderHeight + printRows.length * m.rowHeight + m.footerHeight; @@ -890,7 +910,7 @@ export function renderReport( const statusUtc = Date.UTC(statusDay.getUTCFullYear(), statusDay.getUTCMonth(), statusDay.getUTCDate()); const fullyDone = c >= 1 && finishDay <= statusUtc; const notStarted = c === 0 && startDay >= statusUtc; - if (!fullyDone && !notStarted) px = bx1 + (bx2 - bx1) * c; + if (!fullyDone && !notStarted) px = clampX(bx1 + (bx2 - bx1) * c); } d2d.lineTo(statusLineX!, rowTop); d2d.lineTo(px, rowMid); @@ -977,33 +997,42 @@ export function renderReport( const x = dateToX(date) + zoom / 2; const cy = y + barHeight / 2; const size = barHeight * 0.45; + // Tijdvenster: een ruit buiten het chartgebied wordt niet getekend (geen halve ruit). + const inWindow = !windowed || (x + size >= m.tableWidth && x - size <= canvasWidth); - const advies = colorAdvice(task); - d2d.fillStyle = advies.kind === 'solid' ? advies.fill : advies.segments[0].color; - if (advies.outline) { - // Rode rand om een kritieke mijlpaal in de niet-critical-modi: de ruit omtrekken. - d2d.strokeStyle = advies.outline; - d2d.lineWidth = 1; - } - d2d.beginPath(); - d2d.moveTo(x, cy - size); - d2d.lineTo(x + size, cy); - d2d.lineTo(x, cy + size); - d2d.lineTo(x - size, cy); - d2d.closePath(); - d2d.fill(); - if (advies.outline) d2d.stroke(); - - // Task name label (rechts van de ruit, valt terug naar links/ellipsis bij de rand) - if (options.showTaskNames) { - barLabelJobs.push({ name: task.name, barRightX: x + size, barLeftX: x - size, y: cy + m.s(3), bold: false }); + if (inWindow) { + const advies = colorAdvice(task); + d2d.fillStyle = advies.kind === 'solid' ? advies.fill : advies.segments[0].color; + if (advies.outline) { + // Rode rand om een kritieke mijlpaal in de niet-critical-modi: de ruit omtrekken. + d2d.strokeStyle = advies.outline; + d2d.lineWidth = 1; + } + d2d.beginPath(); + d2d.moveTo(x, cy - size); + d2d.lineTo(x + size, cy); + d2d.lineTo(x, cy + size); + d2d.lineTo(x - size, cy); + d2d.closePath(); + d2d.fill(); + if (advies.outline) d2d.stroke(); + + // Task name label (rechts van de ruit, valt terug naar links/ellipsis bij de rand) + if (options.showTaskNames) { + barLabelJobs.push({ name: task.name, barRightX: x + size, barLeftX: x - size, y: cy + m.s(3), bold: false }); + } } } else if (task.childIds.length > 0) { // Summary bracket bar const start = parseDate(task.time.earlyStart || task.time.scheduleStart); const end = parseDate(task.time.earlyFinish || task.time.scheduleFinish); - const x1 = dateToX(start); - const x2 = dateToX(end) + zoom; + const rawX1 = dateToX(start); + const rawX2 = dateToX(end) + zoom; + // Tijdvenster: geklemd op het chartgebied; een afgekapt uiteinde krijgt geen haakje (dat zou + // een echt begin/einde suggereren). Valt de hele haak buiten het venster, dan niets. + const x1 = clampX(rawX1); + const x2 = clampX(rawX2); + if (windowed && x2 <= x1) continue; const width = Math.max(x2 - x1, 3); const barY = y + barHeight * 0.3; const barH = barHeight * 0.3; @@ -1012,20 +1041,24 @@ export function renderReport( d2d.fillRect(x1, barY, width, barH); // Left triangle - d2d.beginPath(); - d2d.moveTo(x1, barY); - d2d.lineTo(x1, barY + barH + 5); - d2d.lineTo(x1 + 6, barY + barH); - d2d.closePath(); - d2d.fill(); + if (x1 === rawX1) { + d2d.beginPath(); + d2d.moveTo(x1, barY); + d2d.lineTo(x1, barY + barH + 5); + d2d.lineTo(x1 + 6, barY + barH); + d2d.closePath(); + d2d.fill(); + } // Right triangle - d2d.beginPath(); - d2d.moveTo(x1 + width, barY); - d2d.lineTo(x1 + width, barY + barH + 5); - d2d.lineTo(x1 + width - 6, barY + barH); - d2d.closePath(); - d2d.fill(); + if (x2 === rawX2) { + d2d.beginPath(); + d2d.moveTo(x1 + width, barY); + d2d.lineTo(x1 + width, barY + barH + 5); + d2d.lineTo(x1 + width - 6, barY + barH); + d2d.closePath(); + d2d.fill(); + } // Task name label (rechts van de balk, valt terug naar links/ellipsis bij de rand) if (options.showTaskNames) { @@ -1035,9 +1068,15 @@ export function renderReport( // Normal task bar const start = parseDate(task.time.earlyStart || task.time.scheduleStart); const end = parseDate(task.time.earlyFinish || task.time.scheduleFinish); - const x1 = dateToX(start); - const x2 = dateToX(end) + zoom; - const width = Math.max(x2 - x1, 3); + const rawX1 = dateToX(start); + const rawX2 = dateToX(end) + zoom; + const width = Math.max(rawX2 - rawX1, 3); + // Tijdvenster: de balkuiteinden geklemd op het chartgebied; de voortgangsgrens en de + // speling hieronder klemmen op dezelfde manier. Een balk die helemaal buiten het venster + // valt tekent niets (ook geen label). + const x1 = clampX(rawX1); + const x2 = clampX(rawX2); + if (windowed && x2 <= x1) continue; // Kleurmodi (#21) en onderbroken balken (Z15) zijn onafhankelijke dimensies: dezelfde // kleurverhouding komt terug in elk werkblok van één taak. @@ -1047,9 +1086,9 @@ export function renderReport( ? computeSplitSegments(task.splitGaps, start, end, false, calEngine) : [{ start, end }]; const segs = segments.map((s, i) => ({ - x1: i === 0 ? x1 : dateToX(s.start), - x2: i === segments.length - 1 ? x2 : dateToX(s.end), - })); + x1: clampX(i === 0 ? rawX1 : dateToX(s.start)), + x2: clampX(i === segments.length - 1 ? rawX2 : dateToX(s.end)), + })).filter(s => !windowed || s.x2 > s.x1); const split = segs.length > 1; if (split) { @@ -1088,7 +1127,7 @@ export function renderReport( // Eén globale voortgangsgrens over de volle taakduur, maar nooit kleur over de tijdgaten. if (options.showCompletion && task.time.completion > 0) { - const progressEnd = x1 + width * task.time.completion; + const progressEnd = clampX(rawX1 + width * task.time.completion); d2d.fillStyle = 'rgba(0, 0, 0, 0.25)'; for (const s of segs) { const sw = Math.max(s.x2 - s.x1, split ? 2 : 3); @@ -1104,17 +1143,17 @@ export function renderReport( } // Float indicator - if (options.showFloat && task.time.totalFloat > 0 && !task.time.isCritical) { - const floatWidth = task.time.totalFloat * zoom; + const floatEndX = clampX(rawX2 + task.time.totalFloat * zoom); + if (options.showFloat && task.time.totalFloat > 0 && !task.time.isCritical && floatEndX > x2) { d2d.fillStyle = PRINT_COLORS.float + '40'; - d2d.roundRect(x2, y + barHeight * 0.2, floatWidth, barHeight * 0.6, 2); + d2d.roundRect(x2, y + barHeight * 0.2, floatEndX - x2, barHeight * 0.6, 2); d2d.fill(); } // Task name label (rechts van de balk + eventuele speling; valt terug naar links/ellipsis bij de rand) if (options.showTaskNames) { const hasFloat = options.showFloat && task.time.totalFloat > 0 && !task.time.isCritical; - const barRightX = x2 + (hasFloat ? task.time.totalFloat * zoom : 0); + const barRightX = hasFloat ? Math.max(x2, floatEndX) : x2; barLabelJobs.push({ name: task.name, barRightX, barLeftX: x1, y: y + barHeight / 2 + m.s(3), bold: false }); } } @@ -1131,19 +1170,23 @@ export function renderReport( if (baseline.isMilestone) { const x = dateToX(parseDate(baseline.start)) + zoom / 2; const cy = baseY + baseHeight / 2; - d2d.beginPath(); - d2d.moveTo(x, cy - baseHeight); - d2d.lineTo(x + baseHeight, cy); - d2d.lineTo(x, cy + baseHeight); - d2d.lineTo(x - baseHeight, cy); - d2d.closePath(); - d2d.fill(); + if (!windowed || (x + baseHeight >= m.tableWidth && x - baseHeight <= canvasWidth)) { + d2d.beginPath(); + d2d.moveTo(x, cy - baseHeight); + d2d.lineTo(x + baseHeight, cy); + d2d.lineTo(x, cy + baseHeight); + d2d.lineTo(x - baseHeight, cy); + d2d.closePath(); + d2d.fill(); + } } else { - const x1 = dateToX(parseDate(baseline.start)); - const x2 = dateToX(parseDate(baseline.finish)) + zoom; - d2d.beginPath(); - d2d.roundRect(x1, baseY, Math.max(x2 - x1, 2), baseHeight, 1); - d2d.fill(); + const x1 = clampX(dateToX(parseDate(baseline.start))); + const x2 = clampX(dateToX(parseDate(baseline.finish)) + zoom); + if (!windowed || x2 > x1) { + d2d.beginPath(); + d2d.roundRect(x1, baseY, Math.max(x2 - x1, 2), baseHeight, 1); + d2d.fill(); + } } } } diff --git a/src/utils/reportSettings.ts b/src/utils/reportSettings.ts index 42b3f0bf..9ba7de7f 100644 --- a/src/utils/reportSettings.ts +++ b/src/utils/reportSettings.ts @@ -138,18 +138,22 @@ export type TableReportPeriodKey = (typeof TABLE_REPORT_PERIOD_KEYS)[number]; * uitdelen; uit = één doorlopend overlegdocument. `includeUnassigned` neemt de taken zonder * resource als laatste band mee — handig om in een overleg te zien wat nog niemand heeft. * `groupByType` (manuvarkey, punt 2) zet er een laag boven: eerst een band per resourcetype - * (arbeid, ploeg, onderaannemer, materieel, materiaal), daarbinnen per resource. + * (arbeid, ploeg, onderaannemer, materieel, materiaal), daarbinnen per resource. `period` (punt 3) + * is de gedeelde rapportageperiode (issue #120): alleen taken die het venster raken, en de tijdas + * exact op het venster; default `project` = het oude gedrag. */ export interface ResourceGanttReportOptions { pageBreakPerResource: boolean; includeUnassigned: boolean; groupByType: boolean; + period: ReportingPeriod; } export const DEFAULT_RESOURCE_GANTT_OPTIONS: ResourceGanttReportOptions = { pageBreakPerResource: false, includeUnassigned: false, groupByType: false, + period: { preset: 'project' }, }; /** Grenzen van de numerieke opties (de UI en de loader delen ze). */ @@ -335,6 +339,7 @@ function parseResourceGanttOptions(raw: unknown): ResourceGanttReportOptions { pageBreakPerResource: parseBoolean(s.pageBreakPerResource) ?? d.pageBreakPerResource, includeUnassigned: parseBoolean(s.includeUnassigned) ?? d.includeUnassigned, groupByType: parseBoolean(s.groupByType) ?? d.groupByType, + period: parseReportingPeriod(s.period, d.period), }; } diff --git a/tests/browser/resource-gantt-report.spec.ts b/tests/browser/resource-gantt-report.spec.ts index 115f20e6..4681738e 100644 --- a/tests/browser/resource-gantt-report.spec.ts +++ b/tests/browser/resource-gantt-report.spec.ts @@ -62,6 +62,26 @@ test('resourcediagram: rapporttype rendert per resource, opties sturen samenvatt await page.locator('[data-ops-report-option="groupByType"]').check(); await expect(pages).toHaveCount(3); + // Rapportageperiode (manuvarkey punt 3): een eigen bereik dat alleen Fundering raakt ⇒ alleen Ploeg A + // blijft over; Casco (met Kraan) en Gevel vallen buiten de periode en de telling zegt dat. Terug naar + // Hele project herstelt alles — de export hieronder telt dan weer drie pagina's. + const periodField = page.locator('[data-ops-report-period="resourceGanttPeriod"]'); + await periodField.getByLabel(/^(Reporting period:|Rapportageperiode:)$/).click(); + await page.getByRole('option', { name: /^(Custom|Aangepast)$/ }).click(); + const fromInput = page.locator('[data-ops-report-option="resourceGanttPeriod.from"]'); + const toInput = page.locator('[data-ops-report-option="resourceGanttPeriod.to"]'); + await toInput.fill('2026-09-15'); + await fromInput.fill('2026-09-01'); + await expect(count('resources')).toHaveText('1'); + await expect(count('assignments')).toHaveText('1'); + await expect(count('unassigned')).toHaveText('0'); + await expect(count('outsidePeriod')).toHaveText('2'); + await expect(pages).toHaveCount(1); + await periodField.getByLabel(/^(Reporting period:|Rapportageperiode:)$/).click(); + await page.getByRole('option', { name: /^(Project duration|Hele project)$/ }).click(); + await expect(count('outsidePeriod')).toHaveCount(0); + await expect(pages).toHaveCount(3); + // De échte export (vector-tak, zie paginateVector) moet dezelfde drie pagina's opleveren als de // preview: dat is het pad dat de gebruiker in handen krijgt, en de enige plek waar de gedwongen // breekposities de PDF in gaan. diff --git a/tests/planning/check-print-report.ts b/tests/planning/check-print-report.ts index dad02619..9d57fa63 100644 --- a/tests/planning/check-print-report.ts +++ b/tests/planning/check-print-report.ts @@ -498,6 +498,32 @@ const baseOptions = (over: Partial = {}): PrintOptions => ({ const typedForced = measurePrintReport(bandTasks, [], cal, 'Resourcediagram', baseOptions({ rows: typed, pageBreakBeforeGroups: true })); ok(JSON.stringify(typedForced.forcedBreakOffsets) === JSON.stringify([typedForced.headerHeight + 4 * rowH, typedForced.headerHeight + 7 * rowH]), `typelaag: gedwongen posities vóór resource 2 (rij 4) en typeband 2 (rij 7), niet vóór een band direct onder een typekop (got ${JSON.stringify(typedForced.forcedBreakOffsets)})`); + + // Tijdvenster (manuvarkey punt 3): de tijdas loopt exact over het venster (einde inclusief, geen + // marge van 7/14 dagen), balken worden op de chartrand geklemd, en een taak buiten het venster + // tekent geen balk en geen balklabel (zijn tabelrij blijft: welke rijen meedoen beslist de + // rijenbron). Zonder venster byte-identiek. Venster 10–14 jan bij 60 px/dag: de kritieke taak + // (12–16 jan) begint 120 px in de chart — ruimte voor het label links van de balk — en wordt + // rechts afgekapt; de zichtbare (5–9 jan) en de gefilterde (19–23 jan) taak vallen erbuiten. + { + const win = { from: '2026-01-10', to: '2026-01-14' }; + const fixed = baseOptions({ autoFit: false, customZoom: 60, showFloat: false }); + const rec = record(FIX_TASKS, [], cal, { ...fixed, timeWindow: win }); + const d = rec.dims; + ok(Math.abs(d.width - (d.tableWidth + 5 * 60)) < 1e-6, `venster van 5 dagen ⇒ chart 5 × zoom breed (got ${d.width - d.tableWidth})`); + const inBody = (y: number) => y >= d.headerHeight && y < d.height - d.footerHeight; + const bars = rec.roundRects.filter(r => (r.color === CRITICAL || r.color === NORMAL) && inBody(r.y)); + ok(bars.length === 1 && bars[0].color === CRITICAL, `alleen de kritieke taak tekent een balk (got ${bars.length})`); + ok(bars.every(b => b.x >= d.tableWidth - 1e-6 && b.x + b.w <= d.width + 1e-6), 'de balk ligt binnen het chartgebied'); + ok(Math.abs(bars[0].x - (d.tableWidth + 2 * 60)) < 1e-6 && Math.abs(bars[0].x + bars[0].w - d.width) < 1e-6, + 'de balk begint op 12 jan en eindigt op de rechter chartrand, niet erbuiten'); + const chartTexts = rec.texts.filter(t => t.x >= d.tableWidth && inBody(t.y)).map(t => t.text); + ok(chartTexts.some(t => t.startsWith('Kritieke')), `balklabel van de kritieke taak in de chart (got ${JSON.stringify(chartTexts)})`); + ok(!chartTexts.some(t => t.startsWith('Gefilterde') || t.startsWith('Zichtbare')), 'taken buiten het venster: geen balklabel'); + ok(rec.texts.filter(t => t.text === T_HIDDEN.name && t.x < d.tableWidth).length === 1, 'taak buiten het venster houdt zijn tabelrij'); + ok(JSON.stringify(record(FIX_TASKS, [], cal, fixed)) === JSON.stringify(record(FIX_TASKS, [], cal, { ...fixed, timeWindow: undefined })), + 'zonder venster byte-identiek'); + } // Dezelfde drie banden in de overige pagineermodi (review op #132, bevinding 9): zonder // kopherhaling, in 'actual' (1 pt = 1 px, horizontaal getegeld) en met de tijdlijn over twee // paginabreedtes — steeds drie body-rijen die exact op de bandgrenzen eindigen. diff --git a/tests/planning/check-report-resource-gantt-setting.ts b/tests/planning/check-report-resource-gantt-setting.ts index 832b2e94..027d32d2 100644 --- a/tests/planning/check-report-resource-gantt-setting.ts +++ b/tests/planning/check-report-resource-gantt-setting.ts @@ -14,7 +14,7 @@ const expect = (label: string, got: unknown, want: unknown) => { if (JSON.stringify(got) !== JSON.stringify(want)) failures.push(`${label}: verwacht ${JSON.stringify(want)}, kreeg ${JSON.stringify(got)}`); }; -expect('defaults: blad per resource uit, taken zonder resource uit, typelaag uit', DEFAULT_REPORT_SETTINGS.resourceGantt, { pageBreakPerResource: false, includeUnassigned: false, groupByType: false }); +expect('defaults: blad per resource uit, taken zonder resource uit, typelaag uit', DEFAULT_REPORT_SETTINGS.resourceGantt, { pageBreakPerResource: false, includeUnassigned: false, groupByType: false, period: { preset: 'project' } }); expect('defaults: één bron', DEFAULT_REPORT_SETTINGS.resourceGantt, DEFAULT_RESOURCE_GANTT_OPTIONS); expect('isGanttReportType: gantt én resourceGantt', [isGanttReportType('gantt'), isGanttReportType('resourceGantt'), isGanttReportType('milestones'), isGanttReportType('resourceAssignments')], [true, true, false, false]); // Review N6: één predicaat voor "dit type tekent geen relaties" — de forcering van showDeps én het @@ -25,10 +25,10 @@ expect('showDeps staat standaard aan (de forcering doet dus echt iets)', DEFAULT // barColorSelection), dus zonder lijnen is het misleidend — verborgen op hetzelfde predicaat als de relaties. expect('reportTypeShowsCriticalToggle: verborgen waar geen relaties getekend worden', [reportTypeShowsCriticalToggle('gantt'), reportTypeShowsCriticalToggle('resourceGantt'), reportTypeShowsCriticalToggle('milestones')], [true, false, true]); -localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: true, groupByType: true } })); +localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: true, groupByType: true, period: { preset: 'next4Weeks' } } })); const loaded = await loadReportSettings(); expect('opgeslagen rapporttype resourceGantt wordt geladen', loaded.reportType, 'resourceGantt'); -expect('opgeslagen opties worden geladen', loaded.resourceGantt, { pageBreakPerResource: true, includeUnassigned: true, groupByType: true }); +expect('opgeslagen opties worden geladen', loaded.resourceGantt, { pageBreakPerResource: true, includeUnassigned: true, groupByType: true, period: { preset: 'next4Weeks' } }); localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'gantt', showLegend: false })); const zonderBlok = await loadReportSettings(); @@ -37,16 +37,21 @@ expect('… en de overige voorkeuren blijven staan', zonderBlok.showLegend, fals localStorage.setItem('ops-reportSettings', JSON.stringify({ resourceGantt: { pageBreakPerResource: 'ja', includeUnassigned: true } })); const rommel = await loadReportSettings(); -expect('rommel in één veld ⇒ alleen dat veld terug op de default', rommel.resourceGantt, { pageBreakPerResource: false, includeUnassigned: true, groupByType: false }); +expect('rommel in één veld ⇒ alleen dat veld terug op de default', rommel.resourceGantt, { pageBreakPerResource: false, includeUnassigned: true, groupByType: false, period: { preset: 'project' } }); +// Punt 3: de rapportageperiode gaat door dezelfde tolerante parser als de tabelrapporten. +localStorage.setItem('ops-reportSettings', JSON.stringify({ resourceGantt: { period: { preset: 'nonsense' } } })); +expect('onbekende periode-preset ⇒ Hele project', (await loadReportSettings()).resourceGantt.period, { preset: 'project' }); +localStorage.setItem('ops-reportSettings', JSON.stringify({ resourceGantt: { period: { preset: 'custom', from: '2026-09-30', to: '2026-09-01' } } })); +expect('omgekeerd eigen bereik ⇒ Hele project', (await loadReportSettings()).resourceGantt.period, { preset: 'project' }); localStorage.setItem('ops-reportSettings', JSON.stringify({ resourceGantt: 'aan' })); expect('rommel in het hele blok ⇒ defaults', (await loadReportSettings()).resourceGantt, DEFAULT_RESOURCE_GANTT_OPTIONS); localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'resourceDiagram' })); expect('onbekend rapporttype ⇒ default rapporttype', (await loadReportSettings()).reportType, DEFAULT_REPORT_SETTINGS.reportType); -await saveReportSettings({ ...DEFAULT_REPORT_SETTINGS, reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: false, groupByType: true } }); +await saveReportSettings({ ...DEFAULT_REPORT_SETTINGS, reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: false, groupByType: true, period: { preset: 'custom', from: '2026-09-01', to: '2026-09-30' } } }); const saved = await loadReportSettings(); expect('rapporttype overleeft een round-trip', saved.reportType, 'resourceGantt'); -expect('opties overleven een round-trip', saved.resourceGantt, { pageBreakPerResource: true, includeUnassigned: false, groupByType: true }); +expect('opties overleven een round-trip', saved.resourceGantt, { pageBreakPerResource: true, includeUnassigned: false, groupByType: true, period: { preset: 'custom', from: '2026-09-01', to: '2026-09-30' } }); if (failures.length > 0) { console.log(`XX report-resource-gantt-setting: ${failures.length} afwijking(en)`); diff --git a/tests/planning/check-reports.ts b/tests/planning/check-reports.ts index d1950390..a3165a2b 100644 --- a/tests/planning/check-reports.ts +++ b/tests/planning/check-reports.ts @@ -13,7 +13,7 @@ import { type ReportContext, type ReportingPeriod, computeLookAhead, computeCriticalReport, computeProgressReport, computeScheduleHealth, computeResourceLoading, computeResourceAssignments, computeResourceGanttRows, computeWbsSummary, progressState, - remainingDays, resourceBandLabels, taskDepths, + remainingDays, resourceBandLabels, taskDepths, taskFinish, taskStart, isValidReportingPeriod, periodDays, projectSpan, resolveReportingPeriod, weeksToPreset, } from '@/engine/reports'; import { addCalendarMonths, formatDate, parseDate } from '@/utils/dateUtils'; @@ -491,7 +491,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); const r = computeResourceGanttRows(base, opts); eq('resourceGantt: één band per resource, op naam', r.rows.filter(x => x.kind === 'group').map(label), ['Kraan', 'Ploeg 1']); // Ploeg 1 → A, B, D; Kraan → B; zonder resource: C, E (mijlpaal is óók een bladtaak), F, G. - eq('resourceGantt: tellingen', r.counts, { resources: 2, assignments: 4, unassignedTasks: 4 }); + eq('resourceGantt: tellingen', r.counts, { resources: 2, assignments: 4, unassignedTasks: 4, outsidePeriod: 0 }); eq('resourceGantt: Ploeg 1 heeft A, B en D', [...bandTasks(r.rows, 'Ploeg 1')].sort(), [A, B, D].sort()); eq('resourceGantt: Kraan heeft alleen B', bandTasks(r.rows, 'Kraan'), [B]); ok('resourceGantt: B staat onder beide banden', r.rows.filter(x => x.kind === 'task' && x.task.id === B).length === 2); @@ -516,7 +516,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); assignments: [asg('a1', A, jan1.id), asg('a2', A, jan2.id), asg('a3', B, jan2.id)], }, opts); eq('resourceGantt: gelijknamige resources ⇒ twee banden met volgnummer', twins.rows.filter(x => x.kind === 'group').map(label), ['Jan #1', 'Jan #2']); - eq('resourceGantt: gelijknamig — tellingen op identiteit', twins.counts, { resources: 2, assignments: 3, unassignedTasks: 5 }); + eq('resourceGantt: gelijknamig — tellingen op identiteit', twins.counts, { resources: 2, assignments: 3, unassignedTasks: 5, outsidePeriod: 0 }); eq('resourceGantt: gelijknamig — Jan #1 heeft A', bandTasks(twins.rows, 'Jan #1'), [A]); eq('resourceGantt: gelijknamig — Jan #2 heeft A en B', [...bandTasks(twins.rows, 'Jan #2')].sort(), [A, B].sort()); ok('resourceGantt: gelijknamig — rijsleutels uniek', new Set(twins.rows.map(x => x.rowKey)).size === twins.rows.length); @@ -527,7 +527,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); assignments: [asg('a1', A, 'res-leeg')], }, opts); eq('resourceGantt: naamloze resource ⇒ band "#2" (positie in de projectlijst)', naamloos.rows.filter(x => x.kind === 'group').map(label), ['#2']); - eq('resourceGantt: naamloze resource — A telt als toegewezen', naamloos.counts, { resources: 1, assignments: 1, unassignedTasks: 6 }); + eq('resourceGantt: naamloze resource — A telt als toegewezen', naamloos.counts, { resources: 1, assignments: 1, unassignedTasks: 6, outsidePeriod: 0 }); // manuvarkey op #113, punt 2: twee lagen — typeband (vaste volgorde: mensen, dan materieel, dan // materiaal), daarin de resourcebanden op diepte 1, de taken op diepte 2; "(geen)" blijft achteraan @@ -554,6 +554,25 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); }, { ...opts, groupByType: true, typeLabels }); eq('resourceGantt/type: gelijknamigen over twee typen houden hun volgnummer', typedTwins.rows.filter(x => x.kind === 'group').map(x => `${x.depth}:${label(x)}`), ['0:Arbeid', '1:Jan #2', '0:Materieel', '1:Jan #1']); + // manuvarkey op #113, punt 3: tijdvenster — alleen bladtaken die het venster raken (start ≤ tot én + // einde ≥ van, op dagniveau); de tellingen volgen de gefilterde set en `outsidePeriod` telt wat er + // is weggelaten. Zonder venster (Hele project) is outsidePeriod 0 en verandert er niets. + const dayOfTask = (id: string) => ({ s: taskStart(byId(id)).slice(0, 10), f: taskFinish(byId(id)).slice(0, 10) }); + const dayB = dayOfTask(B).s; + const overlapsB = (id: string) => { const { s, f } = dayOfTask(id); return s !== '' && f !== '' && s <= dayB && f >= dayB; }; + const leavesAll = ctx.tasks.filter(t => t.childIds.length === 0); + const windowed = computeResourceGanttRows(base, { ...opts, includeUnassigned: true, window: { from: dayB, to: dayB } }); + ok('resourceGantt/venster: B (start op de vensterdag) staat onder Ploeg 1', bandTasks(windowed.rows, 'Ploeg 1').includes(B)); + ok('resourceGantt/venster: A (klaar vóór het venster) staat er niet', !windowed.rows.some(x => x.kind === 'task' && x.task.id === A)); + ok('resourceGantt/venster: elke getekende taak raakt het venster', windowed.rows.every(x => x.kind !== 'task' || overlapsB(x.task.id))); + ok('resourceGantt/venster: elke bladtaak die het venster raakt staat er ook', leavesAll.filter(t => overlapsB(t.id)).every(t => windowed.rows.some(x => x.kind === 'task' && x.task.id === t.id))); + eq('resourceGantt/venster: outsidePeriod telt de weggelaten bladtaken', windowed.counts.outsidePeriod, leavesAll.filter(t => !overlapsB(t.id)).length); + ok('resourceGantt/venster: tellingen volgen de gefilterde set', windowed.counts.assignments < r.counts.assignments && windowed.counts.unassignedTasks < r.counts.unassignedTasks); + const buiten = computeResourceGanttRows(base, { ...opts, window: { from: '2030-01-01', to: '2030-01-31' } }); + eq('resourceGantt/venster: venster zonder taken ⇒ geen rijen, alles buiten de periode', [buiten.rows.length, buiten.counts.outsidePeriod, buiten.counts.resources], [0, leavesAll.length, 0]); + const ruim = computeResourceGanttRows(base, { ...opts, window: { from: '2020-01-01', to: '2035-12-31' } }); + ok('resourceGantt/venster: een venster dat alles omvat ⇒ dezelfde rijen als zonder venster', JSON.stringify(ruim.rows) === JSON.stringify(r.rows) && ruim.counts.outsidePeriod === 0); + // Twee toewijzingen van dezelfde resource op één taak zijn één rij; een toewijzing aan een // onbekende resource telt niet (die taak is dan "zonder resource", zoals op het scherm). const dubbel = computeResourceGanttRows({ @@ -562,7 +581,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); }, opts); eq('resourceGantt: dubbele toewijzing ⇒ één rij', bandTasks(dubbel.rows, 'Kraan'), [A]); // …maar de telling volgt de records, zoals het tabelrapport Resourcetoewijzingen (review N8). - eq('resourceGantt: onbekende resource ⇒ taak zonder resource; toewijzingen tellen records', dubbel.counts, { resources: 1, assignments: 2, unassignedTasks: 6 }); + eq('resourceGantt: onbekende resource ⇒ taak zonder resource; toewijzingen tellen records', dubbel.counts, { resources: 1, assignments: 2, unassignedTasks: 6, outsidePeriod: 0 }); // Bandvolgorde: taal-/cijferbewust ("Ploeg 2" vóór "Ploeg 10"), hoofdletterongevoelig. const p10 = res('p10', 'Ploeg 10'); const p2 = res('p2', 'ploeg 2'); const aa = res('aa', 'Aannemer'); @@ -593,7 +612,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); // Leeg: geen toewijzingen ⇒ geen rijen; geen taken ⇒ ook geen "(geen)"-band en nultellingen. ok('resourceGantt: geen toewijzingen ⇒ leeg', computeResourceGanttRows({ ...base, assignments: [] }, opts).rows.length === 0); const leeg = computeResourceGanttRows({ tasks: [], resources: base.resources, assignments: base.assignments }, { ...opts, includeUnassigned: true }); - eq('resourceGantt: leeg project ⇒ geen rijen, nultellingen', [leeg.rows.length, leeg.counts], [0, { resources: 0, assignments: 0, unassignedTasks: 0 }]); + eq('resourceGantt: leeg project ⇒ geen rijen, nultellingen', [leeg.rows.length, leeg.counts], [0, { resources: 0, assignments: 0, unassignedTasks: 0, outsidePeriod: 0 }]); } // ── Uitslag ────────────────────────────────────────────────────────────────────────────────────── From 8fdeb52d6c3cc81e72dc7f358f14a00c71c3cd93 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 15:38:52 +0000 Subject: [PATCH 3/7] Rapport: eenheden/dag en curve per rij in het resourcediagram (#113, manuvarkey punt 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per taakrij onder een resourceband levert `computeResourceGanttRows` nu de toewijzing van die band op die taak (`assignmentByRowKey`, per `rowKey`): eenheden per dag opgeteld over records van dezelfde resource, de curve alleen als alle records dezelfde hebben (afwezig telt als UNIFORM), anders null. Rijen onder "(geen)" hebben geen entry. De render tekent dat als twee tabelkolommen direct achter de naam (`PrintOptions.assignmentColumns` + `rowAssignments` + `curveLabels`): eenheden rechts uitgelijnd (geheel zonder decimalen, anders tot twee), de vertaalde curve links en afgekort met `fitText`, een streepje bij verschillende curves; kolomkoppen via `labels.tableHeaders.unitsPerDay`/`curve`. De tabel wordt precies de twee kolombreedtes (45 + 75) breder; `buildPrintRows` hangt de toewijzing aan `PrintRow.assignment`. Zonder de optie byte-identiek, ook mét meegegeven `rowAssignments`. De preview-signatuur serialiseert de Map expliciet (anders `{}`, dus geen herrender bij een gewijzigde inzet). Optie `showAssignmentColumns` standaard aan (dit is de informatie waarvoor het rapport wordt uitgedeeld), tolerante parser; drie sleutels in 14 locales (twee kolomkoppen, één optielabel); curvenamen uit `common:resource.curve.*`; gidsen nl+en; CLAUDE.md-rapportparagraaf voor de drie nieuwe opties. Regressie: check-reports (optelling, UNIFORM-terugval, null bij verschillende curves, entries exact voor de taakrijen onder resourcebanden), check-print-report (tabelbreedte, celteksten per kolom, koppen, zonder optie byte-identiek), settings-check, browserspec (standaard aan; uitzetten rastert opnieuw). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq --- CLAUDE.md | 9 +- public/docs/en/gids-rapporten-printen.md | 7 +- public/docs/nl/gids-rapporten-printen.md | 6 ++ src/components/panels/ReportPanel.tsx | 30 ++++++- src/engine/reports/resourceGantt.ts | 38 ++++++++- src/i18n/locales/ar/report.json | 3 + src/i18n/locales/de/report.json | 3 + src/i18n/locales/en/report.json | 3 + src/i18n/locales/es/report.json | 3 + src/i18n/locales/fa/report.json | 3 + src/i18n/locales/fr/report.json | 3 + src/i18n/locales/it/report.json | 3 + src/i18n/locales/ja/report.json | 3 + src/i18n/locales/ko/report.json | 3 + src/i18n/locales/nl/report.json | 3 + src/i18n/locales/pl/report.json | 3 + src/i18n/locales/pt/report.json | 3 + src/i18n/locales/tr/report.json | 3 + src/i18n/locales/zh/report.json | 3 + src/services/print/printPreview.ts | 82 ++++++++++++++++--- src/utils/reportSettings.ts | 7 +- tests/browser/resource-gantt-report.spec.ts | 10 +++ tests/planning/check-print-report.ts | 33 ++++++++ .../check-report-resource-gantt-setting.ts | 12 +-- tests/planning/check-reports.ts | 25 +++++- 25 files changed, 274 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3f331533..4198c83b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -155,7 +155,14 @@ start; relaties staan bij dit type uit omdat een taak onder meerdere banden kan relatielijnen en legendaregel, de balken volgen `barColorSelection` via `criticalFill`); optie "blad per resource" = `PrintOptions.pageBreakBeforeGroups` → `RenderReportResult.forcedBreakOffsets` → `forcedBreakOffsetsPx` in `tileLayout`, waar een gedwongen -positie zonder vulgraaddrempel wint; `isGanttReportType()` bundelt beide Gantt-achtige typen; de voet +positie zonder vulgraaddrempel wint — een band direct onder een band (de optionele typelaag +`groupByType`: eerst een band per resourcetype in de vaste volgorde `RESOURCE_TYPE_BAND_ORDER`) krijgt +geen eigen gedwongen positie; optie *Rapportageperiode* = de gedeelde `ReportingPeriod` als +`PrintOptions.timeWindow` (tijdas exact op het venster, geometrie geklemd op het chartgebied want +`Draw2D` kent geen clip; de rijenbron filtert op overlap en telt `counts.outsidePeriod`); optie +*Eenheden/dag en curve tonen* = `PrintOptions.assignmentColumns` + `rowAssignments` (per `rowKey` +uit `assignmentByRowKey`: eenheden opgeteld, curve alleen bij eensluidende records) als twee +tabelkolommen achter de naam; `isGanttReportType()` bundelt beide Gantt-achtige typen; de voet met legenda is sinds #113 net als de kop een herhaalbaar blok — `RenderReportResult.footerHeight` → `repeatFooterHeightPx`/`repeatFooter`, instelling `repeatFooter` standaard aan; let op: de preview rendert per pagina één volledige `renderReport`-pass extra voor die strook, net als voor de kop), het diff --git a/public/docs/en/gids-rapporten-printen.md b/public/docs/en/gids-rapporten-printen.md index fe3c7bcd..1f62bf69 100644 --- a/public/docs/en/gids-rapporten-printen.md +++ b/public/docs/en/gids-rapporten-printen.md @@ -111,7 +111,12 @@ only tasks that touch the window take part, the time axis runs exactly from the date, and a bar that continues beyond it is cut off at the edge. That lets a meeting put the look-ahead report and the resource diagram side by side over exactly the same weeks. *Project duration* (the default) is the usual behaviour; with a window the summary block also counts how -many tasks fall outside it. +many tasks fall outside it. After the task name two columns **Units/d** and **Curve** are shown by +default: how many units per day the band's resource is assigned to the task, and with which +distribution curve — "you are on Foundations half days, front loaded" says more than "you are on +Foundations". A task under two bands shows each band's own load; two assignments of the same +resource on one task are added up, and a dash in the curve column means those assignments carry +different curves. Turn **Show units/day and curve** off for a narrower table. The settings of the Gantt print apply here too — critical path, float, bar colours, status line, paper, header repeat — with three exceptions: *Follow view* (this report doesn't take its rows from diff --git a/public/docs/nl/gids-rapporten-printen.md b/public/docs/nl/gids-rapporten-printen.md index 702b5074..3fb74d21 100644 --- a/public/docs/nl/gids-rapporten-printen.md +++ b/public/docs/nl/gids-rapporten-printen.md @@ -114,6 +114,12 @@ venster raken doen mee, de tijdas loopt precies van de begin- tot de einddatum, verder doorloopt wordt aan de rand afgekapt. Zo leg je in dezelfde vergadering het look-ahead-rapport en het resourcediagram over exact dezelfde weken naast elkaar. *Hele project* (standaard) is het gewone gedrag; bij een venster telt het overzichtsblok ook hoeveel taken erbuiten vallen. +Achter de taaknaam staan standaard twee kolommen **Eenh./d** en **Curve**: hoeveel eenheden per +dag de resource van die band op de taak staat en met welke verdeelcurve — "je staat halve dagen +op Fundering, vooraan belast" zegt meer dan "je staat op Fundering". Een taak onder twee banden +toont per band de eigen inzet; twee toewijzingen van dezelfde resource op één taak worden +opgeteld, en een streepje bij de curve betekent dat die toewijzingen verschillende curves hebben. +Zet **Eenheden/dag en curve tonen** uit voor een smallere tabel. De instellingen van de Gantt-afdruk gelden hier ook — kritiek pad, speling, balkkleuren, statuslijn, papier, kopherhaling — op drie na: *Volg weergave* (de rijen komen bij dit rapport niet van het diff --git a/src/components/panels/ReportPanel.tsx b/src/components/panels/ReportPanel.tsx index 93b51c83..87f23542 100644 --- a/src/components/panels/ReportPanel.tsx +++ b/src/components/panels/ReportPanel.tsx @@ -454,6 +454,13 @@ export function ReportPanel() { SUBCONTRACTOR: tCommon('resource.type.subcontractor'), EQUIPMENT: tCommon('resource.type.equipment'), MATERIAL: tCommon('resource.type.material'), }), [tCommon]); + // Vertaalde curvenamen voor de toewijzingskolommen (punt 1): dezelfde sleutels als het taakraster. + const curveLabels = useMemo(() => ({ + UNIFORM: tCommon('resource.curve.uniform'), FRONT_LOADED: tCommon('resource.curve.frontLoaded'), + BACK_LOADED: tCommon('resource.curve.backLoaded'), BELL: tCommon('resource.curve.bell'), + EARLY_PEAK: tCommon('resource.curve.earlyPeak'), LATE_PEAK: tCommon('resource.curve.latePeak'), + DOUBLE_PEAK: tCommon('resource.curve.doublePeak'), TURTLE: tCommon('resource.curve.turtle'), + }), [tCommon]); // Rapportageperiode als tijdvenster (punt 3): dezelfde oplossing als het control toont; bij // *Hele project* geen venster, zodat het rapport byte-identiek blijft aan vóór deze optie. const resourceGanttPeriod = useResolvedPeriod(resourceGanttOptions.period); @@ -572,6 +579,8 @@ export function ReportPanel() { tableHeaders: { wbs: t('tableHeaders.wbs'), taskName: t('tableHeaders.taskName'), + unitsPerDay: t('tableHeaders.unitsPerDay'), + curve: t('tableHeaders.curve'), start: t('tableHeaders.start'), end: t('tableHeaders.end'), duration: t('tableHeaders.duration'), @@ -616,6 +625,10 @@ export function ReportPanel() { pageBreakBeforeGroups: reportType === 'resourceGantt' && resourceGanttOptions.pageBreakPerResource, // Punt 3: de tijdas op de rapportageperiode (alleen resourcediagram, alleen buiten *Hele project*). timeWindow: resourceGanttWindow, + // Punt 1: eenheden/dag en curve van de band op de taak als tabelkolommen (alleen resourcediagram). + assignmentColumns: reportType === 'resourceGantt' && resourceGanttOptions.showAssignmentColumns, + rowAssignments: resourceGantt?.assignmentByRowKey, + curveLabels, barColorsLegendLabels: { criticalOutline: t('legend.criticalOutline', { defaultValue: 'Kritiek pad (rand)' }), categoriesMore: (n: number) => t('legend.categoriesMore', { count: n }), @@ -626,7 +639,7 @@ export function ReportPanel() { cpmResult, barColorSelection, fieldCtx.activityCodeTypes, fieldCtx.customFieldDefs, reportTaskTypeLabels, tTask, statusLine, statusDate, resources, assignments, baselineOverlay, reportRows, reportType, resourceGanttOptions.pageBreakPerResource, tasks.length, - resourceGantt, resourceGanttWindow]); + resourceGantt, resourceGanttWindow, resourceGanttOptions.showAssignmentColumns, curveLabels]); // `options` bevat afgeleide catalogus-/vertaalobjecten die bij een lokale preview-state-update // opnieuw kunnen worden aangemaakt zonder dat hun inhoud wijzigde. De rastertaak gebruikt deze // inhoudssignatuur als effectgrens: anders start `setPreviewPages` zelf opnieuw pagina 0 en 1. @@ -638,7 +651,10 @@ export function ReportPanel() { ? (value as ViewRow[]).map(r => (r.kind === 'group' ? `g:${r.key}:${r.label}:${r.count}:${r.depth}` : `t:${r.rowKey}:${r.depth}:${r.dimmed ? 1 : 0}`)) - : value + // Een Map serialiseert als `{}`; de toewijzingskolommen (punt 1) moeten wél een herrender geven. + : key === 'rowAssignments' && value instanceof Map + ? [...(value as Map).entries()] + : value )), [options]); // Eén generatie beheert één layout + één begrensde renderqueue. Een optiewijziging annuleert het @@ -1439,6 +1455,16 @@ export function ReportPanel() { /> {t('resourceGantt.groupByType')} + {/* Punt 3: de gedeelde rapportageperiode (issue #120) als tijdvenster van dit rapport. */} ; counts: { /** Resources (op identiteit) met minstens één bladtaak. */ resources: number; @@ -85,6 +94,14 @@ export interface ResourceGanttRowsResult { }; } +/** De toewijzing achter één taakrij van een resourceband (zie de moduledoc). */ +export interface RowAssignment { + /** Som van `unitsPerDay` over de records van deze resource op deze taak. */ + unitsPerDay: number; + /** De curve als alle records dezelfde hebben (`undefined` in een record = UNIFORM), anders null. */ + curve: ResourceCurve | null; +} + /** Bandsleutel van de "(geen)"-band — dezelfde codering als de schermgroepering. */ const NONE_BAND_KEY = encodeBandKey([NONE_RAWKEY]); @@ -164,6 +181,8 @@ export function computeResourceGanttRows( // een toewijzing aan een onbekende resource telt niet (zelfde regel als `resourceNames` op het // scherm: die taak is dan "zonder resource"). const tasksByResource = new Map>(); + // Per resource-id × taak-id de opgetelde eenheden en de verzameling curves (punt 1). + const loadByResourceTask = new Map }>(); const assignedTaskIds = new Set(); let assignments = 0; for (const a of ctx.assignments) { @@ -174,6 +193,11 @@ export function computeResourceGanttRows( if (!bucket) { bucket = new Map(); tasksByResource.set(a.resourceId, bucket); } bucket.set(task.id, task); assignedTaskIds.add(task.id); + const loadKey = `${a.resourceId}\u0000${task.id}`; + const load = loadByResourceTask.get(loadKey) ?? { unitsPerDay: 0, curves: new Set() }; + load.unitsPerDay += a.unitsPerDay; + load.curves.add(a.curve ?? 'UNIFORM'); + loadByResourceTask.set(loadKey, load); } const labels = resourceBandLabels(ctx.resources, opts.locale); @@ -185,6 +209,7 @@ export function computeResourceGanttRows( .sort((a, b) => collator.compare(a.label, b.label) || a.index - b.index); const rows: ViewRow[] = []; + const assignmentByRowKey = new Map(); // Eén resourceband met zijn taakrijen, onder een optioneel typepad (diepte +1). const pushBand = (band: (typeof bands)[number], path: string[]) => { const groupPath = [...path, band.id]; @@ -194,7 +219,15 @@ export function computeResourceGanttRows( depth: path.length, levelIndex: path.length, collapsed: false, }); for (const task of sortByStart([...band.tasks.values()])) { - rows.push({ kind: 'task', rowKey: encodeGroupedTaskRowKey(groupPath, task.id), task, depth: path.length + 1, dimmed: false }); + const rowKey = encodeGroupedTaskRowKey(groupPath, task.id); + rows.push({ kind: 'task', rowKey, task, depth: path.length + 1, dimmed: false }); + const load = loadByResourceTask.get(`${band.id}\u0000${task.id}`); + if (load) { + assignmentByRowKey.set(rowKey, { + unitsPerDay: load.unitsPerDay, + curve: load.curves.size === 1 ? [...load.curves][0] : null, + }); + } } }; if (opts.groupByType) { @@ -232,6 +265,7 @@ export function computeResourceGanttRows( return { rows, + assignmentByRowKey, counts: { resources: bands.length, assignments, unassignedTasks: unassigned.length, outsidePeriod: allLeaves.length - leaves.length }, }; } diff --git a/src/i18n/locales/ar/report.json b/src/i18n/locales/ar/report.json index 82d87ac3..70f66eac 100644 --- a/src/i18n/locales/ar/report.json +++ b/src/i18n/locales/ar/report.json @@ -67,6 +67,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "وحدة/يوم", + "curve": "المنحنى", "taskName": "اسم المهمة", "start": "البداية", "end": "النهاية", @@ -147,6 +149,7 @@ "pageBreakPerResource": "كل مورد في صفحة جديدة", "includeUnassigned": "تضمين المهام بدون مورد", "groupByType": "تجميع حسب نوع المورد", + "showAssignmentColumns": "إظهار الوحدات/اليوم والمنحنى", "emptyPeriod": "لا توجد مهام في فترة التقرير — اختر فترة أخرى أو المشروع بأكمله.", "outsidePeriod": "خارج الفترة:", "empty": "لا توجد تعيينات موارد — عيّن الموارد للمهام أولاً.", diff --git a/src/i18n/locales/de/report.json b/src/i18n/locales/de/report.json index f70003d3..462542cb 100644 --- a/src/i18n/locales/de/report.json +++ b/src/i18n/locales/de/report.json @@ -55,6 +55,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "Einh./T", + "curve": "Kurve", "taskName": "Aufgabenname", "start": "Anfang", "end": "Ende", @@ -135,6 +137,7 @@ "pageBreakPerResource": "Jede Ressource auf einer neuen Seite", "includeUnassigned": "Aufgaben ohne Ressource einbeziehen", "groupByType": "Nach Ressourcentyp gruppieren", + "showAssignmentColumns": "Einheiten/Tag und Kurve anzeigen", "emptyPeriod": "Keine Vorgänge im Berichtszeitraum — wählen Sie einen anderen Zeitraum oder Gesamtes Projekt.", "outsidePeriod": "Außerhalb des Zeitraums:", "empty": "Keine Ressourcenzuweisungen — weisen Sie zuerst Ressourcen zu Aufgaben zu.", diff --git a/src/i18n/locales/en/report.json b/src/i18n/locales/en/report.json index a655e8e7..415157c9 100644 --- a/src/i18n/locales/en/report.json +++ b/src/i18n/locales/en/report.json @@ -55,6 +55,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "Units/d", + "curve": "Curve", "taskName": "Task Name", "start": "Start", "end": "End", @@ -135,6 +137,7 @@ "pageBreakPerResource": "Each resource on a new page", "includeUnassigned": "Include tasks without a resource", "groupByType": "Group by resource type", + "showAssignmentColumns": "Show units/day and curve", "emptyPeriod": "No tasks in the reporting period — choose another period or Whole project.", "outsidePeriod": "Outside the period:", "empty": "No resource assignments — assign resources to tasks first.", diff --git a/src/i18n/locales/es/report.json b/src/i18n/locales/es/report.json index c77ed1de..ee350964 100644 --- a/src/i18n/locales/es/report.json +++ b/src/i18n/locales/es/report.json @@ -58,6 +58,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "Unid./d", + "curve": "Curva", "taskName": "Nombre de tarea", "start": "Inicio", "end": "Fin", @@ -138,6 +140,7 @@ "pageBreakPerResource": "Cada recurso en una página nueva", "includeUnassigned": "Incluir tareas sin recurso", "groupByType": "Agrupar por tipo de recurso", + "showAssignmentColumns": "Mostrar unidades/día y curva", "emptyPeriod": "No hay tareas en el período del informe — elija otro período o Proyecto completo.", "outsidePeriod": "Fuera del período:", "empty": "No hay asignaciones de recursos — asigne primero recursos a las tareas.", diff --git a/src/i18n/locales/fa/report.json b/src/i18n/locales/fa/report.json index 61e067f3..353c25ae 100644 --- a/src/i18n/locales/fa/report.json +++ b/src/i18n/locales/fa/report.json @@ -56,6 +56,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "واحد/روز", + "curve": "منحنی", "taskName": "نام وظیفه", "start": "شروع", "end": "پایان", @@ -136,6 +138,7 @@ "pageBreakPerResource": "هر منبع در صفحه‌ای جدید", "includeUnassigned": "شامل کردن کارهای بدون منبع", "groupByType": "گروه‌بندی بر اساس نوع منبع", + "showAssignmentColumns": "نمایش واحد/روز و منحنی", "emptyPeriod": "هیچ کاری در دوره گزارش وجود ندارد — دوره دیگری یا کل پروژه را انتخاب کنید.", "outsidePeriod": "خارج از دوره:", "empty": "هیچ تخصیص منبعی وجود ندارد — ابتدا منابع را به کارها تخصیص دهید.", diff --git a/src/i18n/locales/fr/report.json b/src/i18n/locales/fr/report.json index 40bdc1ac..aad5aa38 100644 --- a/src/i18n/locales/fr/report.json +++ b/src/i18n/locales/fr/report.json @@ -58,6 +58,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "Unités/j", + "curve": "Courbe", "taskName": "Nom de la tâche", "start": "Début", "end": "Fin", @@ -138,6 +140,7 @@ "pageBreakPerResource": "Chaque ressource sur une nouvelle page", "includeUnassigned": "Inclure les tâches sans ressource", "groupByType": "Grouper par type de ressource", + "showAssignmentColumns": "Afficher unités/jour et courbe", "emptyPeriod": "Aucune tâche dans la période de rapport — choisissez une autre période ou Projet entier.", "outsidePeriod": "Hors période :", "empty": "Aucune affectation de ressource — affectez d'abord des ressources aux tâches.", diff --git a/src/i18n/locales/it/report.json b/src/i18n/locales/it/report.json index b1d12837..b5beec51 100644 --- a/src/i18n/locales/it/report.json +++ b/src/i18n/locales/it/report.json @@ -58,6 +58,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "Unità/g", + "curve": "Curva", "taskName": "Nome attività", "start": "Inizio", "end": "Fine", @@ -138,6 +140,7 @@ "pageBreakPerResource": "Ogni risorsa su una nuova pagina", "includeUnassigned": "Includi le attività senza risorsa", "groupByType": "Raggruppa per tipo di risorsa", + "showAssignmentColumns": "Mostra unità/giorno e curva", "emptyPeriod": "Nessuna attività nel periodo del report — scegli un altro periodo o Intero progetto.", "outsidePeriod": "Fuori dal periodo:", "empty": "Nessuna assegnazione di risorse — assegna prima le risorse alle attività.", diff --git a/src/i18n/locales/ja/report.json b/src/i18n/locales/ja/report.json index b24fbb90..1813d42e 100644 --- a/src/i18n/locales/ja/report.json +++ b/src/i18n/locales/ja/report.json @@ -52,6 +52,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "単位/日", + "curve": "カーブ", "taskName": "タスク名", "start": "開始", "end": "終了", @@ -132,6 +134,7 @@ "pageBreakPerResource": "リソースごとに改ページ", "includeUnassigned": "リソース未割当のタスクを含める", "groupByType": "リソース種別でグループ化", + "showAssignmentColumns": "単位/日とカーブを表示", "emptyPeriod": "レポート期間内にタスクがありません — 別の期間またはプロジェクト全体を選択してください。", "outsidePeriod": "期間外:", "empty": "リソースの割当がありません — まずタスクにリソースを割り当ててください。", diff --git a/src/i18n/locales/ko/report.json b/src/i18n/locales/ko/report.json index 0c2de207..5d0036f1 100644 --- a/src/i18n/locales/ko/report.json +++ b/src/i18n/locales/ko/report.json @@ -52,6 +52,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "단위/일", + "curve": "곡선", "taskName": "작업 이름", "start": "시작", "end": "종료", @@ -132,6 +134,7 @@ "pageBreakPerResource": "자원마다 새 페이지", "includeUnassigned": "자원이 없는 작업 포함", "groupByType": "자원 유형별로 그룹화", + "showAssignmentColumns": "단위/일 및 곡선 표시", "emptyPeriod": "보고 기간에 작업이 없습니다 — 다른 기간 또는 전체 프로젝트를 선택하세요.", "outsidePeriod": "기간 외:", "empty": "자원 배정이 없습니다 — 먼저 작업에 자원을 배정하세요.", diff --git a/src/i18n/locales/nl/report.json b/src/i18n/locales/nl/report.json index 2aeb6529..5d1f739f 100644 --- a/src/i18n/locales/nl/report.json +++ b/src/i18n/locales/nl/report.json @@ -55,6 +55,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "Eenh./d", + "curve": "Curve", "taskName": "Taaknaam", "start": "Start", "end": "Einde", @@ -135,6 +137,7 @@ "pageBreakPerResource": "Elke resource op een nieuwe pagina", "includeUnassigned": "Taken zonder resource meenemen", "groupByType": "Groeperen op resourcetype", + "showAssignmentColumns": "Eenheden/dag en curve tonen", "emptyPeriod": "Geen taken in de rapportageperiode — kies een andere periode of Hele project.", "outsidePeriod": "Buiten de periode:", "empty": "Geen resourcetoewijzingen — wijs eerst resources toe aan taken.", diff --git a/src/i18n/locales/pl/report.json b/src/i18n/locales/pl/report.json index 96248c08..1f642953 100644 --- a/src/i18n/locales/pl/report.json +++ b/src/i18n/locales/pl/report.json @@ -61,6 +61,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "Jedn./d", + "curve": "Krzywa", "taskName": "Nazwa zadania", "start": "Początek", "end": "Koniec", @@ -141,6 +143,7 @@ "pageBreakPerResource": "Każdy zasób na nowej stronie", "includeUnassigned": "Uwzględnij zadania bez zasobu", "groupByType": "Grupuj według typu zasobu", + "showAssignmentColumns": "Pokaż jednostki/dzień i krzywą", "emptyPeriod": "Brak zadań w okresie raportu — wybierz inny okres lub Cały projekt.", "outsidePeriod": "Poza okresem:", "empty": "Brak przypisań zasobów — najpierw przypisz zasoby do zadań.", diff --git a/src/i18n/locales/pt/report.json b/src/i18n/locales/pt/report.json index c9291aee..616c5811 100644 --- a/src/i18n/locales/pt/report.json +++ b/src/i18n/locales/pt/report.json @@ -58,6 +58,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "Unid./d", + "curve": "Curva", "taskName": "Nome da tarefa", "start": "Início", "end": "Fim", @@ -138,6 +140,7 @@ "pageBreakPerResource": "Cada recurso numa nova página", "includeUnassigned": "Incluir tarefas sem recurso", "groupByType": "Agrupar por tipo de recurso", + "showAssignmentColumns": "Mostrar unidades/dia e curva", "emptyPeriod": "Nenhuma tarefa no período do relatório — escolha outro período ou Projeto inteiro.", "outsidePeriod": "Fora do período:", "empty": "Sem atribuições de recursos — atribua primeiro recursos às tarefas.", diff --git a/src/i18n/locales/tr/report.json b/src/i18n/locales/tr/report.json index c0e398a9..b76699da 100644 --- a/src/i18n/locales/tr/report.json +++ b/src/i18n/locales/tr/report.json @@ -55,6 +55,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "Birim/g", + "curve": "Eğri", "taskName": "Görev adı", "start": "Başlangıç", "end": "Bitiş", @@ -135,6 +137,7 @@ "pageBreakPerResource": "Her kaynak yeni bir sayfada", "includeUnassigned": "Kaynaksız görevleri dahil et", "groupByType": "Kaynak türüne göre grupla", + "showAssignmentColumns": "Birim/gün ve eğriyi göster", "emptyPeriod": "Rapor döneminde görev yok — başka bir dönem veya Tüm proje seçin.", "outsidePeriod": "Dönem dışında:", "empty": "Kaynak ataması yok — önce görevlere kaynak atayın.", diff --git a/src/i18n/locales/zh/report.json b/src/i18n/locales/zh/report.json index 12903c53..6bf76303 100644 --- a/src/i18n/locales/zh/report.json +++ b/src/i18n/locales/zh/report.json @@ -52,6 +52,8 @@ }, "tableHeaders": { "wbs": "WBS", + "unitsPerDay": "单位/天", + "curve": "曲线", "taskName": "任务名称", "start": "开始", "end": "结束", @@ -132,6 +134,7 @@ "pageBreakPerResource": "每个资源另起一页", "includeUnassigned": "包含未分配资源的任务", "groupByType": "按资源类型分组", + "showAssignmentColumns": "显示单位/天和曲线", "emptyPeriod": "报告期间内没有任务 — 请选择其他期间或整个项目。", "outsidePeriod": "期间之外:", "empty": "没有资源分配——请先为任务分配资源。", diff --git a/src/services/print/printPreview.ts b/src/services/print/printPreview.ts index 5fe22355..a2a1ff1a 100644 --- a/src/services/print/printPreview.ts +++ b/src/services/print/printPreview.ts @@ -26,10 +26,11 @@ import { visibleBarColorCategories, type BarColorContext, } from '@/services/print/barColorCategories'; -import type { Resource, ResourceAssignment } from '@/types/resource'; +import type { Resource, ResourceAssignment, ResourceCurve } from '@/types/resource'; import type { ActivityCodeType, CustomFieldDef } from '@/types/structure'; import type { BarColorSelection } from '@/types/barColor'; import type { ViewRow } from '@/engine/view/visibleRows'; +import type { RowAssignment } from '@/engine/reports/resourceGantt'; import type { BaselineOverlay } from '@/types/baseline'; // BASISmaten bij rapport-lettergrootte 100%. Niets tekent hier nog rechtstreeks mee: alle @@ -89,6 +90,10 @@ const COL = { start: { w: 55 }, end: { w: 55 }, complete: { w: 45 }, + // Toewijzingskolommen van het resourcediagram (manuvarkey punt 1): eenheden per dag en de + // verdeelcurve van de resource van de band op die taak. Alleen bij `assignmentColumns`. + units: { w: 45 }, + curve: { w: 75 }, }; /** @@ -120,20 +125,24 @@ function resolveNameColumnWidth(raw: number | undefined): number { * **Voltooiing tonen** uit verdwijnt de hele Volt.-kolom uit de tabel (issue #93) — niet alleen * de waarden — dus krimpt de tabel met precies die kolombreedte en krijgt de tijdlijn die ruimte. */ -function tableWidthFor(showCompletion: boolean, nameW: number): number { - return COL.wbs.w + nameW + COL.duration.w + COL.start.w + COL.end.w + (showCompletion ? COL.complete.w : 0); +function tableWidthFor(showCompletion: boolean, nameW: number, assignmentColumns = false): number { + return COL.wbs.w + nameW + COL.duration.w + COL.start.w + COL.end.w + (showCompletion ? COL.complete.w : 0) + + (assignmentColumns ? COL.units.w + COL.curve.w : 0); } // Kolomposities van links naar rechts. `k` is de rapport-lettergrootteschaal (zie // {@link ReportMetrics}); álle kolommaten schalen mee, want een grotere letter heeft een bredere // kolom nodig. Bij k = 1 is dit rekenkundig exact de ongeschaalde uitkomst. `complete` is // `undefined` wanneer de kolom verborgen is; alle tekenpaden lezen dat als "niet tekenen". -function getColPositions(k: number, showCompletion: boolean, nameW: number) { +function getColPositions(k: number, showCompletion: boolean, nameW: number, assignmentColumns = false) { let x = 0; const next = (w: number) => { const col = { x, w: w * k }; x += w * k; return col; }; return { wbs: next(COL.wbs.w), name: next(nameW), + // Direct achter de naam: ze horen bij "wie staat hierop en hoe", niet bij de datums. + units: assignmentColumns ? next(COL.units.w) : undefined, + curve: assignmentColumns ? next(COL.curve.w) : undefined, duration: next(COL.duration.w), start: next(COL.start.w), end: next(COL.end.w), @@ -153,6 +162,8 @@ export interface PrintRow { depth: number; label?: string; // groepsband-label count?: number; // groepsband-aantal bladrijen + /** Toewijzing van de band op deze taak (resourcediagram, `PrintOptions.rowAssignments`). */ + assignment?: RowAssignment; } /** @@ -161,11 +172,15 @@ export interface PrintRow { * de volledige takenboom (oud gedrag, self-flatten), met wezen zonder gevonden ouder achteraan. * Geëxporteerd omdat het rapportpaneel dezelfde rijen nodig heeft om de naamkolom te meten. */ -export function buildPrintRows(tasks: Task[], rows: ViewRow[] | undefined): PrintRow[] { +export function buildPrintRows( + tasks: Task[], + rows: ViewRow[] | undefined, + rowAssignments?: ReadonlyMap, +): PrintRow[] { const printRows: PrintRow[] = []; if (rows) { for (const row of rows) { - if (row.kind === 'task') printRows.push({ kind: 'task', task: row.task, depth: row.depth }); + if (row.kind === 'task') printRows.push({ kind: 'task', task: row.task, depth: row.depth, assignment: rowAssignments?.get(row.rowKey) }); else printRows.push({ kind: 'group', depth: row.depth, label: row.label, count: row.count }); } return printRows; @@ -270,7 +285,12 @@ export const REPORT_FONT_SCALES = [90, 100, 110, 125] as const; * geen enkele Select kan tonen en die na een herstart dus niet reproduceerbaar is. Zelfde semantiek * als in de settings- en rapport-loaders, allemaal via {@link snapToChoice}. */ -function makeMetrics(reportFontScale: number | undefined, showCompletion: boolean, taskNameColumnWidth: number | undefined): ReportMetrics { +function makeMetrics( + reportFontScale: number | undefined, + showCompletion: boolean, + taskNameColumnWidth: number | undefined, + assignmentColumns = false, +): ReportMetrics { const pct = snapToChoice(REPORT_FONT_SCALES, reportFontScale ?? 100) ?? 100; const k = pct / 100; const nameW = resolveNameColumnWidth(taskNameColumnWidth); @@ -286,9 +306,9 @@ function makeMetrics(reportFontScale: number | undefined, showCompletion: boolea // Bewust de SOM van de twee geschaalde hoogtes, niet `(PROJECT + TIMELINE) * k`: alleen zo valt // de kopstrook-grens gegarandeerd tot op de bit samen met waar de tijdschaal-kop eindigt. totalHeaderHeight: projectHeaderHeight + timelineHeaderHeight, - tableWidth: tableWidthFor(showCompletion, nameW) * k, + tableWidth: tableWidthFor(showCompletion, nameW, assignmentColumns) * k, footerHeight: FOOTER_HEIGHT * k, - cols: getColPositions(k, showCompletion, nameW), + cols: getColPositions(k, showCompletion, nameW, assignmentColumns), }; } @@ -327,7 +347,11 @@ export interface PrintOptions { * bindend-informatie beschikbaar is (zie {@link PrintOptions.drivingSequenceIds}). */ relationStyle: string; }; - tableHeaders: { wbs: string; taskName: string; start: string; end: string; duration: string; completion: string }; + tableHeaders: { + wbs: string; taskName: string; start: string; end: string; duration: string; completion: string; + /** Koppen van de toewijzingskolommen (resourcediagram); ontbreken ⇒ Nederlandse terugval. */ + unitsPerDay?: string; curve?: string; + }; /** Label boven de gestippelde "vandaag"-lijn in het Gantt-gebied. */ today: string; /** Label boven de statusdatum-/voortgangslijn in de exportkop (#54). */ @@ -371,6 +395,18 @@ export interface PrintOptions { * wordt alleen aangeboden op het resourcediagram, dat er geen tekent. Afwezig ⇒ byte-identiek. */ timeWindow?: { from: string; to: string }; + /** + * OPTIONEEL — TOEWIJZINGSKOLOMMEN (manuvarkey op #113, punt 1; resourcediagram): twee extra + * tabelkolommen direct achter de naam — eenheden per dag en verdeelcurve van de resource van de + * band op die taak — gevuld uit `rowAssignments` (per `ViewRow.rowKey`, uit + * `computeResourceGanttRows().assignmentByRowKey`). Een rij zonder entry (bandrij, "(geen)") + * laat de cellen leeg. De tabel wordt precies de twee kolombreedtes breder; afwezig ⇒ + * byte-identiek. + */ + assignmentColumns?: boolean; + rowAssignments?: ReadonlyMap; + /** Vertaalde curvenamen (`common:resource.curve.*`); een ontbrekend label valt terug op de enum. */ + curveLabels?: Partial>; /** * Lettergrootte van het GEGENEREERDE RAPPORT als percentage (issue #25 punt 4). 100 (of * ontbrekend) = het oude gedrag, byte-identiek. Werkt bewust RELATIEF: tekst, rijhoogtes, @@ -460,6 +496,11 @@ function formatDuration(days: number): string { return `${days}d`; } +/** Eenheden per dag: geheel als "2", anders tot twee decimalen zonder nullen ("0.5", "1.25"). */ +function formatUnitsPerDay(units: number): string { + return String(Math.round(units * 100) / 100); +} + /** Format completion as "75%" */ function formatCompletion(completion: number): string { return `${Math.round(completion * 100)}%`; @@ -635,11 +676,11 @@ export function renderReport( ): RenderReportResult { // Alle maatvoering loopt via dit object — de tekenhelpers lezen de module-constanten niet meer // rechtstreeks (zie {@link ReportMetrics} voor het waarom van relatief-schalen). - const m = makeMetrics(options.reportFontScale, options.showCompletion, options.taskNameColumnWidth); + const m = makeMetrics(options.reportFontScale, options.showCompletion, options.taskNameColumnWidth, !!options.assignmentColumns); // Rijen-bron: zie {@link buildPrintRows} — taakrijen mét diepte plus groepsband-rijen (#54) die // als samenvattings-strook tekenen. - const printRows = buildPrintRows(tasks, options.rows); + const printRows = buildPrintRows(tasks, options.rows, options.rowAssignments); const flatTasks: PrintTask[] = printRows .filter((r): r is PrintRow & { kind: 'task'; task: Task } => r.kind === 'task') .map(r => ({ ...r.task, _depth: r.depth })); @@ -1832,8 +1873,10 @@ function drawTimelineHeader( d2d.textAlign = 'left'; d2d.fillText(th?.taskName ?? 'Taaknaam', cols.name.x + m.s(4), headerY); + if (cols.curve) d2d.fillText(th?.curve ?? 'Curve', cols.curve.x + m.s(4), headerY); d2d.textAlign = 'center'; + if (cols.units) d2d.fillText(th?.unitsPerDay ?? 'Eenh./d', cols.units.x + cols.units.w / 2, headerY); d2d.fillText(th?.duration ?? 'Duur', cols.duration.x + cols.duration.w / 2, headerY); d2d.fillText(th?.start ?? 'Start', cols.start.x + cols.start.w / 2, headerY); d2d.fillText(th?.end ?? 'Einde', cols.end.x + cols.end.w / 2, headerY); @@ -1846,6 +1889,7 @@ function drawTimelineHeader( d2d.lineWidth = 0.5; const colBorders = [cols.name.x, cols.duration.x, cols.start.x, cols.end.x, m.tableWidth]; if (cols.complete) colBorders.push(cols.complete.x); + if (cols.units && cols.curve) colBorders.push(cols.units.x, cols.curve.x); for (const cx of colBorders) { d2d.beginPath(); d2d.moveTo(cx, top); @@ -1870,7 +1914,7 @@ function drawTimelineHeader( function drawTaskTable( d2d: Draw2D, m: ReportMetrics, - printRows: { kind: 'task' | 'group'; task?: Task; depth: number; label?: string; count?: number }[], + printRows: PrintRow[], canvasHeight: number, cols: ColPositions, options: PrintOptions, @@ -1943,6 +1987,17 @@ function drawTaskTable( const nameAvail = cols.name.x + cols.name.w - m.s(NAME_RIGHT_PAD) - nameX; d2d.fillText(fitText(d2d, task.name, nameAvail), nameX, textY); + // Toewijzingskolommen (resourcediagram): eenheden rechts uitgelijnd, de curve links en afgekort. + if (cols.units && cols.curve && row.assignment) { + d2d.fillStyle = PRINT_COLORS.textSecondary; + d2d.font = m.font(8); + d2d.textAlign = 'right'; + d2d.fillText(formatUnitsPerDay(row.assignment.unitsPerDay), cols.units.x + cols.units.w - cellPad, textY); + d2d.textAlign = 'left'; + const curveText = row.assignment.curve === null ? '—' : (options.curveLabels?.[row.assignment.curve] ?? row.assignment.curve); + d2d.fillText(fitText(d2d, curveText, cols.curve.w - 2 * cellPad), cols.curve.x + cellPad, textY); + } + // Duration d2d.fillStyle = PRINT_COLORS.textSecondary; d2d.font = m.font(8); @@ -1978,6 +2033,7 @@ function drawTaskTable( d2d.lineWidth = 0.5; const colBorders = [cols.name.x, cols.duration.x, cols.start.x, cols.end.x]; if (cols.complete) colBorders.push(cols.complete.x); + if (cols.units && cols.curve) colBorders.push(cols.units.x, cols.curve.x); for (const cx of colBorders) { d2d.beginPath(); d2d.moveTo(cx, m.totalHeaderHeight); diff --git a/src/utils/reportSettings.ts b/src/utils/reportSettings.ts index 9ba7de7f..51df4e2c 100644 --- a/src/utils/reportSettings.ts +++ b/src/utils/reportSettings.ts @@ -140,13 +140,16 @@ export type TableReportPeriodKey = (typeof TABLE_REPORT_PERIOD_KEYS)[number]; * `groupByType` (manuvarkey, punt 2) zet er een laag boven: eerst een band per resourcetype * (arbeid, ploeg, onderaannemer, materieel, materiaal), daarbinnen per resource. `period` (punt 3) * is de gedeelde rapportageperiode (issue #120): alleen taken die het venster raken, en de tijdas - * exact op het venster; default `project` = het oude gedrag. + * exact op het venster; default `project` = het oude gedrag. `showAssignmentColumns` (punt 1) + * zet achter de taaknaam twee kolommen met eenheden per dag en verdeelcurve van de resource van + * de band — standaard aan: dat is de informatie waarvoor je dit rapport uitdeelt. */ export interface ResourceGanttReportOptions { pageBreakPerResource: boolean; includeUnassigned: boolean; groupByType: boolean; period: ReportingPeriod; + showAssignmentColumns: boolean; } export const DEFAULT_RESOURCE_GANTT_OPTIONS: ResourceGanttReportOptions = { @@ -154,6 +157,7 @@ export const DEFAULT_RESOURCE_GANTT_OPTIONS: ResourceGanttReportOptions = { includeUnassigned: false, groupByType: false, period: { preset: 'project' }, + showAssignmentColumns: true, }; /** Grenzen van de numerieke opties (de UI en de loader delen ze). */ @@ -340,6 +344,7 @@ function parseResourceGanttOptions(raw: unknown): ResourceGanttReportOptions { includeUnassigned: parseBoolean(s.includeUnassigned) ?? d.includeUnassigned, groupByType: parseBoolean(s.groupByType) ?? d.groupByType, period: parseReportingPeriod(s.period, d.period), + showAssignmentColumns: parseBoolean(s.showAssignmentColumns) ?? d.showAssignmentColumns, }; } diff --git a/tests/browser/resource-gantt-report.spec.ts b/tests/browser/resource-gantt-report.spec.ts index 4681738e..2e691411 100644 --- a/tests/browser/resource-gantt-report.spec.ts +++ b/tests/browser/resource-gantt-report.spec.ts @@ -62,6 +62,16 @@ test('resourcediagram: rapporttype rendert per resource, opties sturen samenvatt await page.locator('[data-ops-report-option="groupByType"]').check(); await expect(pages).toHaveCount(3); + // Toewijzingskolommen (manuvarkey punt 1): standaard aan; uitzetten maakt de tabel smaller en + // rastert de preview dus opnieuw. + const colsToggle = page.locator('[data-ops-report-option="showAssignmentColumns"]'); + await expect(colsToggle).toBeChecked(); + const firstImg = pages.first().locator('img'); + const beforeCols = await firstImg.getAttribute('src'); + await colsToggle.uncheck(); + await expect.poll(() => firstImg.getAttribute('src'), { timeout: 20_000 }).not.toBe(beforeCols); + await colsToggle.check(); + // Rapportageperiode (manuvarkey punt 3): een eigen bereik dat alleen Fundering raakt ⇒ alleen Ploeg A // blijft over; Casco (met Kraan) en Gevel vallen buiten de periode en de telling zegt dat. Terug naar // Hele project herstelt alles — de export hieronder telt dan weer drie pagina's. diff --git a/tests/planning/check-print-report.ts b/tests/planning/check-print-report.ts index 9d57fa63..f94e0df2 100644 --- a/tests/planning/check-print-report.ts +++ b/tests/planning/check-print-report.ts @@ -524,6 +524,39 @@ const baseOptions = (over: Partial = {}): PrintOptions => ({ ok(JSON.stringify(record(FIX_TASKS, [], cal, fixed)) === JSON.stringify(record(FIX_TASKS, [], cal, { ...fixed, timeWindow: undefined })), 'zonder venster byte-identiek'); } + + // Toewijzingskolommen (manuvarkey punt 1): twee kolommen direct achter de naam (x 180–225 en + // 225–300 bij de standaardnaamkolom van 130), gevuld per rijsleutel; de tabel wordt precies de + // twee kolombreedtes breder; zonder de optie (ook mét `rowAssignments`) byte-identiek. + { + const aBands: ViewRow[] = [ + { kind: 'group', rowKey: 'b', key: 'b', label: 'Metselaar', count: 2, depth: 0, levelIndex: 0, collapsed: false }, + { kind: 'task', rowKey: 'b/norm', task: T_NORM, depth: 1, dimmed: false }, + { kind: 'task', rowKey: 'b/crit', task: T_CRIT, depth: 1, dimmed: false }, + ]; + const rowAssignments = new Map([ + ['b/norm', { unitsPerDay: 1.5, curve: 'FRONT_LOADED' as const }], + ['b/crit', { unitsPerDay: 2, curve: null }], + ]); + const plainA = record([T_NORM, T_CRIT], [], cal, baseOptions({ rows: aBands })); + const withCols = record([T_NORM, T_CRIT], [], cal, baseOptions({ + rows: aBands, assignmentColumns: true, rowAssignments, curveLabels: { FRONT_LOADED: 'Vooraan belast' }, + })); + ok(withCols.dims.tableWidth === plainA.dims.tableWidth + 45 + 75, `tabel precies twee kolommen breder (got +${withCols.dims.tableWidth - plainA.dims.tableWidth})`); + const inCol = (t: { x: number; y: number }, x0: number, x1: number) => t.x >= x0 && t.x <= x1 && t.y > withCols.dims.headerHeight && t.y < withCols.dims.height - withCols.dims.footerHeight; + const unitsTexts = withCols.texts.filter(t => inCol(t, 180, 225)).map(t => t.text).sort(); + const curveTexts = withCols.texts.filter(t => inCol(t, 225, 300)).map(t => t.text).sort(); + ok(JSON.stringify(unitsTexts) === JSON.stringify(['1.5', '2']), `eenheden per rij in de kolom (got ${JSON.stringify(unitsTexts)})`); + // De testmeter rekent 6 px per teken, dus "Vooraan belast" (84 px) kapt in de 75 px-kolom af op + // een beletselteken — dat is het bedoelde `fitText`-gedrag; met het echte 8 px-font past het. + ok(curveTexts.length === 2 && curveTexts.some(t => t.startsWith('Vooraan be')) && curveTexts.includes('—'), + `vertaalde curve (desnoods afgekort), streepje bij verschillende curves (got ${JSON.stringify(curveTexts)})`); + const heads = withCols.texts.filter(t => t.y <= withCols.dims.headerHeight).map(t => t.text); + ok(heads.includes('Eenh./d') && heads.includes('Curve'), 'kolomkoppen aanwezig (Nederlandse terugval zonder label)'); + ok(!plainA.texts.some(t => t.text === 'Eenh./d' || t.text === 'Vooraan belast'), 'zonder optie geen kolommen'); + ok(JSON.stringify(record([T_NORM, T_CRIT], [], cal, baseOptions({ rows: aBands, rowAssignments }))) === JSON.stringify(plainA), + 'rowAssignments zonder assignmentColumns ⇒ byte-identiek'); + } // Dezelfde drie banden in de overige pagineermodi (review op #132, bevinding 9): zonder // kopherhaling, in 'actual' (1 pt = 1 px, horizontaal getegeld) en met de tijdlijn over twee // paginabreedtes — steeds drie body-rijen die exact op de bandgrenzen eindigen. diff --git a/tests/planning/check-report-resource-gantt-setting.ts b/tests/planning/check-report-resource-gantt-setting.ts index 027d32d2..a3567c0b 100644 --- a/tests/planning/check-report-resource-gantt-setting.ts +++ b/tests/planning/check-report-resource-gantt-setting.ts @@ -14,7 +14,7 @@ const expect = (label: string, got: unknown, want: unknown) => { if (JSON.stringify(got) !== JSON.stringify(want)) failures.push(`${label}: verwacht ${JSON.stringify(want)}, kreeg ${JSON.stringify(got)}`); }; -expect('defaults: blad per resource uit, taken zonder resource uit, typelaag uit', DEFAULT_REPORT_SETTINGS.resourceGantt, { pageBreakPerResource: false, includeUnassigned: false, groupByType: false, period: { preset: 'project' } }); +expect('defaults: blad per resource uit, taken zonder resource uit, typelaag uit', DEFAULT_REPORT_SETTINGS.resourceGantt, { pageBreakPerResource: false, includeUnassigned: false, groupByType: false, period: { preset: 'project' }, showAssignmentColumns: true }); expect('defaults: één bron', DEFAULT_REPORT_SETTINGS.resourceGantt, DEFAULT_RESOURCE_GANTT_OPTIONS); expect('isGanttReportType: gantt én resourceGantt', [isGanttReportType('gantt'), isGanttReportType('resourceGantt'), isGanttReportType('milestones'), isGanttReportType('resourceAssignments')], [true, true, false, false]); // Review N6: één predicaat voor "dit type tekent geen relaties" — de forcering van showDeps én het @@ -25,10 +25,10 @@ expect('showDeps staat standaard aan (de forcering doet dus echt iets)', DEFAULT // barColorSelection), dus zonder lijnen is het misleidend — verborgen op hetzelfde predicaat als de relaties. expect('reportTypeShowsCriticalToggle: verborgen waar geen relaties getekend worden', [reportTypeShowsCriticalToggle('gantt'), reportTypeShowsCriticalToggle('resourceGantt'), reportTypeShowsCriticalToggle('milestones')], [true, false, true]); -localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: true, groupByType: true, period: { preset: 'next4Weeks' } } })); +localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: true, groupByType: true, period: { preset: 'next4Weeks' }, showAssignmentColumns: false } })); const loaded = await loadReportSettings(); expect('opgeslagen rapporttype resourceGantt wordt geladen', loaded.reportType, 'resourceGantt'); -expect('opgeslagen opties worden geladen', loaded.resourceGantt, { pageBreakPerResource: true, includeUnassigned: true, groupByType: true, period: { preset: 'next4Weeks' } }); +expect('opgeslagen opties worden geladen', loaded.resourceGantt, { pageBreakPerResource: true, includeUnassigned: true, groupByType: true, period: { preset: 'next4Weeks' }, showAssignmentColumns: false }); localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'gantt', showLegend: false })); const zonderBlok = await loadReportSettings(); @@ -37,7 +37,7 @@ expect('… en de overige voorkeuren blijven staan', zonderBlok.showLegend, fals localStorage.setItem('ops-reportSettings', JSON.stringify({ resourceGantt: { pageBreakPerResource: 'ja', includeUnassigned: true } })); const rommel = await loadReportSettings(); -expect('rommel in één veld ⇒ alleen dat veld terug op de default', rommel.resourceGantt, { pageBreakPerResource: false, includeUnassigned: true, groupByType: false, period: { preset: 'project' } }); +expect('rommel in één veld ⇒ alleen dat veld terug op de default', rommel.resourceGantt, { pageBreakPerResource: false, includeUnassigned: true, groupByType: false, period: { preset: 'project' }, showAssignmentColumns: true }); // Punt 3: de rapportageperiode gaat door dezelfde tolerante parser als de tabelrapporten. localStorage.setItem('ops-reportSettings', JSON.stringify({ resourceGantt: { period: { preset: 'nonsense' } } })); expect('onbekende periode-preset ⇒ Hele project', (await loadReportSettings()).resourceGantt.period, { preset: 'project' }); @@ -48,10 +48,10 @@ expect('rommel in het hele blok ⇒ defaults', (await loadReportSettings()).reso localStorage.setItem('ops-reportSettings', JSON.stringify({ reportType: 'resourceDiagram' })); expect('onbekend rapporttype ⇒ default rapporttype', (await loadReportSettings()).reportType, DEFAULT_REPORT_SETTINGS.reportType); -await saveReportSettings({ ...DEFAULT_REPORT_SETTINGS, reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: false, groupByType: true, period: { preset: 'custom', from: '2026-09-01', to: '2026-09-30' } } }); +await saveReportSettings({ ...DEFAULT_REPORT_SETTINGS, reportType: 'resourceGantt', resourceGantt: { pageBreakPerResource: true, includeUnassigned: false, groupByType: true, period: { preset: 'custom', from: '2026-09-01', to: '2026-09-30' }, showAssignmentColumns: false } }); const saved = await loadReportSettings(); expect('rapporttype overleeft een round-trip', saved.reportType, 'resourceGantt'); -expect('opties overleven een round-trip', saved.resourceGantt, { pageBreakPerResource: true, includeUnassigned: false, groupByType: true, period: { preset: 'custom', from: '2026-09-01', to: '2026-09-30' } }); +expect('opties overleven een round-trip', saved.resourceGantt, { pageBreakPerResource: true, includeUnassigned: false, groupByType: true, period: { preset: 'custom', from: '2026-09-01', to: '2026-09-30' }, showAssignmentColumns: false }); if (failures.length > 0) { console.log(`XX report-resource-gantt-setting: ${failures.length} afwijking(en)`); diff --git a/tests/planning/check-reports.ts b/tests/planning/check-reports.ts index a3165a2b..847216a2 100644 --- a/tests/planning/check-reports.ts +++ b/tests/planning/check-reports.ts @@ -20,7 +20,7 @@ import { addCalendarMonths, formatDate, parseDate } from '@/utils/dateUtils'; import { makeMonthLabeler } from '@/utils/monthLabel'; import { DEFAULT_TABLE_REPORT_OPTIONS, parseReportingPeriod, parseTableReportOptions } from '@/utils/reportSettings'; import type { Task } from '@/types/task'; -import type { Resource, ResourceAssignment } from '@/types/resource'; +import type { Resource, ResourceAssignment, ResourceCurve } from '@/types/resource'; const S = () => useAppStore.getState(); const diffs: string[] = []; @@ -573,6 +573,29 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); const ruim = computeResourceGanttRows(base, { ...opts, window: { from: '2020-01-01', to: '2035-12-31' } }); ok('resourceGantt/venster: een venster dat alles omvat ⇒ dezelfde rijen als zonder venster', JSON.stringify(ruim.rows) === JSON.stringify(r.rows) && ruim.counts.outsidePeriod === 0); + // manuvarkey op #113, punt 1: per taakrij de toewijzing van de band op die taak — eenheden opgeteld + // over records van dezelfde resource, curve alleen als alle records dezelfde hebben (afwezig = + // UNIFORM), anders null; taakrijen onder "(geen)" hebben geen entry. + const asgFull = (id: string, taskId: string, resourceId: string, unitsPerDay: number, curve?: ResourceCurve): ResourceAssignment => ({ id, taskId, resourceId, unitsPerDay, curve }); + const loaded = computeResourceGanttRows({ + tasks: ctx.tasks, resources: [kraan, jan1], + assignments: [ + asgFull('l1', A, kraan.id, 0.5, 'FRONT_LOADED'), asgFull('l2', A, kraan.id, 1, 'FRONT_LOADED'), + asgFull('l3', B, kraan.id, 2), asgFull('l4', B, jan1.id, 1, 'BELL'), asgFull('l5', B, jan1.id, 1, 'BACK_LOADED'), + ], + }, { ...opts, includeUnassigned: true }); + const rowOf = (bandName: string, taskId: string) => { + const start = loaded.rows.findIndex(x => label(x) === bandName); + return loaded.rows.slice(start + 1).find(x => x.kind === 'task' && x.task.id === taskId)!; + }; + eq('resourceGantt/toewijzing: Kraan op A — twee records opgeteld, zelfde curve', loaded.assignmentByRowKey.get(rowOf('Kraan', A).rowKey), { unitsPerDay: 1.5, curve: 'FRONT_LOADED' }); + eq('resourceGantt/toewijzing: Kraan op B — record zonder curve telt als UNIFORM', loaded.assignmentByRowKey.get(rowOf('Kraan', B).rowKey), { unitsPerDay: 2, curve: 'UNIFORM' }); + eq('resourceGantt/toewijzing: Jan op B — verschillende curves ⇒ null', loaded.assignmentByRowKey.get(rowOf('Jan', B).rowKey), { unitsPerDay: 2, curve: null }); + const noneStart = loaded.rows.findIndex(x => label(x) === '(geen)'); + ok('resourceGantt/toewijzing: elke taakrij onder een resourceband heeft een entry', loaded.rows.slice(0, noneStart).every(x => x.kind !== 'task' || loaded.assignmentByRowKey.has(x.rowKey))); + ok('resourceGantt/toewijzing: "(geen)"-rijen hebben geen entry', noneStart > 0 && loaded.rows.slice(noneStart).every(x => x.kind !== 'task' || !loaded.assignmentByRowKey.has(x.rowKey))); + eq('resourceGantt/toewijzing: aantal entries = aantal taakrijen onder resourcebanden', loaded.assignmentByRowKey.size, loaded.rows.slice(0, noneStart).filter(x => x.kind === 'task').length); + // Twee toewijzingen van dezelfde resource op één taak zijn één rij; een toewijzing aan een // onbekende resource telt niet (die taak is dan "zonder resource", zoals op het scherm). const dubbel = computeResourceGanttRows({ From 3caeb4e9587767d22632cfcd702d9d74cdb7d24a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 15:40:08 +0000 Subject: [PATCH 4/7] artifacts: screenshots typelaag/toewijzingskolommen en rapportageperiode van het resourcediagram (#113) Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq --- .../04-typelaag-en-toewijzingskolommen.png | Bin 0 -> 131534 bytes .../05-rapportageperiode-14-sep-tot-11-okt.png | Bin 0 -> 123897 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 artifacts/resourcediagram/04-typelaag-en-toewijzingskolommen.png create mode 100644 artifacts/resourcediagram/05-rapportageperiode-14-sep-tot-11-okt.png diff --git a/artifacts/resourcediagram/04-typelaag-en-toewijzingskolommen.png b/artifacts/resourcediagram/04-typelaag-en-toewijzingskolommen.png new file mode 100644 index 0000000000000000000000000000000000000000..76cb2d3602d2e9687507c618247693753a7da231 GIT binary patch literal 131534 zcmd43RaBfy*Dgvz0>J|W2p-%eKyV8XToT+J8h3{V65NB+Xz<|f?(Xj1(6}^C!(pxO z&suw5d}Ew5&c4`n^^Tfv&5|iqHS4Jg`6@4ghD?AA2M32HB`Kx|2ZtaG2M53R=H+t^ z5`OM99Nb$tDY4JWZmEYWh{D)_F2qxs6n!kp&sSLZFE`;3nU|Y1CA74(N{DCGnyikz z`Buw3PZkY=bZToORg0WYzQYqS;t#G;3S(haw$UI_2n&A|pMEOi)ILs6Y0zeV;=d;1 zBe$S^@m$^C$xd1Jud4q}>+Ns#{}6mdMSWlYA)uqZ!2R>h&GAb8&-eQH|60JP?&GjA z8$QyK{^r|G)&*$p(;N)oKKe_^03RearqBB3Pch+qi%;!YkofN%MV~_D#M1`Ms$Z#6 z%lKrwC+!sEA`*+eM=foa>LP1Qz*n`c(26hqQSJAY*1`3<{HSZkVQNzG&GPtSTM+lJ zA}zN}Nik9!2Oh|;0}rkLX}flfDm){F!4!1x*BtAc3Iw$T#33V5HJ#uu@XUT9Bla7F z3#b$g^h)_-No>v@Lk~rTh5Px=Yr8w(pDu(PZl^!@GQC>ak5r3hg3u3#Mq6b|aTEIp z*yuuC<)!_~{OOFb#6 zfnOb>=NAq%vPr4w-rq9K)Z{7Og_3w0enRWyAV34|&-Phw|Nbb2H(F$2(XWCpNPacj zaA7XJ1A*igl+0Mvp!rc^NiEl((G5t^&%~>pdUS*B0XxOj%2pwF#I+WgNIU zppI)%R#D5~I04+RT_k>(K*PR;bCY1%y5?zs$g?if z{G~2xMG>6k@2RuD(H{1^jdDP0iSJ>>OxF8o{zI4pA+T3C1K&ahYf#=xI;prZStGB= z7G8!DOGR=>>+0#T$@Z~oGkS-C4IX!%UR+M3Bc=$Cf~$J5&TGgJ{LH*4ndm zsi@yW>6-IVM($Y&y8Kstfk)elzyUuoR>-hMio1~Mehq$~8r(85S1!8vww|5W3&D|4? zfZup-lz29uLqkShdVi(KwM_R%AY1FB_xJC>u{1f#BjLpF^4p!ajI+udi=en|5qUtX!k7ZobkC?aH!%$0gQa(UJ;AkpK!Q zyVHF0>pDC<91k})i<-vEwnt+$1(v+rz`%fO$dp7$X(m!)b=D8@qB+5)y!lf2W6f4C z%{K}8PJygNgghkKlp2kx->67w#1ojrzway<2FH7UCIBB1%y}2gMdO zx;_<%#;l1_+g>{ibj+(Xxk%6RI=pQ+-TZTmZu;ksT`!$McqoX`%NWYq}{n`ZcDB6BkM>a7Mq5);pTL!s~34#o|^Mf zla{TNTS>fXH&)%!vAQIDww%vnbZo4ShDO6?w8TNeu7zdyc#`>62BFnGDghDjdXwsi zXl2=~rKh1=EZJb)bd*f4w0_(ZyPKnW|K&#V zCO2UXVIk)C{F@jr@&ajv8*hq0cic3=A}Go9+DgxW^Nr+az|=x~j_cRq_79k`%#o#R z2M;`s#XU~ow`q#m6y3k0^U~*2EYby+YEFJ;ZjcaVf6pyyPO_s;!TCrXlS9(>Ra{~Z zht)7i4FXYePyJ;85C57d0b4>X)r-DDc!Bb2^E@?4HbGYcp4Pc4DO9keM5W67N6&_? z!52t+r(>4VhqR3bJY6j0FJHa{umeVH_RKl(i|85H*(c3?C3)qyHy33-{F+FzexE;M zmX_vui2})pj~3?@UHj$zJjMImLL?5RUVMYM1qlSXr1AhgR<12e1OmttTY-M1HcX4; zMBI+L96U|O*=sS3ZSGaqQJQD-%L}3kCM4g+)qaivP@03f(ZRb!JT|2XU*k5Ca(P}xa$g^*ZkFj>m+}_jX{$@zzfl> zUW}`axHP$H*j+oO#0U}2k=$TR6e+*sz{65~tmkxpJtnLthxK+lq&vsK@+)|ogvaZl z{LxUyVDmdLKQuWHApfFE0(~8iSSXMc#fC)tDWr%bV_W6nL`K1@o=ZXIRR@Y5%x$KV zMvc1Ei;K%}qxsDjFM1Xm`P4*4@4j9p&yi16^_#)s$AO4gTB)hwi2xdVqnC=D-VNEMfpH&<4Yl74+_=wlfde)Ihk?f+PzPBn z>}8%D>RYSU6kV#PR<8TH26_T?t{4^qbk#6H#nsSD*Vx<$dOxV!OnTf1ps@0U*lj=N$OQ8qR*GHemMs?oR349W4mp6za zI*tK#R34QHKJm94h z#3TrI>meYKc5%WYbi{N4YO_jj5@B|@#^j@$bSZwil#O03&|?r_nyEjG6cHl4lWX+s zpw*+{EMQ`oIWysFeXPkWP0&rR-OA?x?RXVbN=m=H^%9vD{Px(GBNq}-;}PR(VSD!z zAD;ilJ=aB=o*w~WFSa&=eyiT|XINXSCjsf(=gEAoXqJVnTKk>4hKd{oQT*pC(X%6Q znNJLX1Kq}~sNw=c-DC)U1Iy^R1#J==GkPMDj5XfDH5zUg5ojSIWIkInYQ=sC=meF< z5MYj9a|;l6rD}d^$Q!2$zsmHf3Altq)5%(=ZKIq1Jne}+)i8e5(n`t5SX;{FGAGSz z?~2WD)}~f+fO-qsJj8h2tK>oB6G!!&h>x`#_Qk#@vwk7l5DbJ;cuIegZCxujFle3h zC#qq)ci8#;J-e?SPfb-V5m&=P?;{U?9^1uDKr_9c%+rS7%jI*$^PzBfppLuA4llr! z1z=)Op*imQE0G%3AEJoX-DeVN0{A>1 z=&5+j*MEJMNf7+{?VJ2mONXdo>pi%rx_bKe@6XSxm#5X{5GT$|1!cH4Xm@6HpzVvD zJ|WKm#pr0_w}K9n?J_(eG&dU*wn&L4KzzMCz-fF?0{T6M-#ydBqd5Csc}zg7cCbR5km_h@hN&tYQpS^+@(l}=P<~5Mw$=95IA2L9 z{ja(WHpbJLx05;3}F}~g2RT5&t!_U3LTHK=iYNFMeC12kY^HFP- zzvNKR_!A4*U+DUQL)z+@uO?+E9rD2iQZ(^cCiGxn=$pH}4Pi_6b(b>S3Y2JV-oI>d zVL_diHjzeC_<+gb{mKrO-*&TcvGuAUgl=)ZXKXC=-gBml)O0ZgOc2q6PvuctE2--U zL3P{;Yj8514Va+6FxJ$CZs%gWvt1$dq2DP}ON4!)NbLxNf9P>4=jJx{^~qp+a!2WV zw|l%S1J7uGTHH{FNF-P%FmcflwVHw_%z^x6`{zf03CWN4SENd6mGf)OsF^w36j8(4 zf;Ql!jC3#XP}MA|j8ts4dl(IJtU`;o81);_MLWD>zn(@6e#omHh2Z8T0Nw(Ikc{ zpXqmZcVW(zRS`Aq3tpH_PA7>z>BCdE!(RFu{p+zlMM!#ksSmq=1LRx36U~p#+LGtC z;5F~VD=U3~SsT7%AnoJhO^?&-CBEYdoULSLsgmM;&Cd&TZdTgAHV<|0!iZpUVYHL` zlUy~`vLn~HmF72q{G9nN6~^S6hi{pZhs@N8kGBp-t=eAl&;CR^YS9dS=pi@#q+6+A zhC@j)uOTvicajd(T7vX&iDwFLE<%E=X2n&}r)>fb<>{;!-B~@? z1kk&(SuazI=(i2h-L9+7t`6q`56jw3w05IZ^v;_pWYYI@fP01O{Dv9wFb!1(uv|CRH4%#fi0C7th1mbQ3C1^t{_n1u5;X_4?=` zuBk7!v~X8*YS$dg>z(d*Nhj0A-UF|do(%-0gRC%1$8wG&A2ZqW5T-C1N?8ANuhiz1 zDsF6oEU?rhTWP3R&-bKm4VRW@<+`%>!Y`YjfFAu6NWI2x-fX#8ruP`@qQd0xLc7fW zWisVJXc%D?atTM7*k=*SAG!(f<>%W$7;Vi}>ilOtHD$6OK1cE{SJmGRV?$%(UZI2y zdtqaV%;|NX7QDp9w~v9B8w#V*WKqSi)2kPv4-K_1aHAyF94m}^x-Ew{8_4(}(cOkr zXN4I?0~1jhUh6XEx~1WKn-E)}bbKQE4NaTs39sbTn*;&BJNw<*O39p?Q!Rz&3idhb zpb69Cr<>SyfU0cU1qjm>OU}NxfN2fby3yZ&c2XMlcy{~qW-J3})aKId$W)8H#N{%& z4JUV=tfKG`Ge~^ak4L(4Ji{HgMe4a!{MC?Hnw<1*@1hAXLflGa(q@8f#;$Ozpz&bl zr0svR-SBgu3tnnf!IMx6f6GJO3*6HrT0^sTd(iCBFQ9#gOuMg(9uRIS{U~B!1P4&! z?qM~K_LU6eFITo>Q=xzFwHsz+NC8RUv4}M=4`Z(i9r!s=lpcqCjTbbm!R;%-@zQ2$ zEvg<2{BQ~;8>t62i;!|%Wb61Y_NG(u~zjtEYqqs3XzUq-B{4; zV5pKUEHLblkxPrnedvL8p{p@vsN!gjz&QUMd1Ou@Ljv6eC;8(Zp=>T%K94< zIpbfu9|gk1x096pzh@w_s{VPx;#rePm4h1opRpMoGbRbJyR=FB7X6zb$xAI)mzSPS zGqH@gMVV2n`Zo_-7)wF{fuZbv2pDHj=)+#uHS)!U7A|%|vR1aqM~$2n+?5t%J136V z1vhrV>U(otIFhkGov**DTRJx5ZLa{72q}R~;U2lw zA7nJ`G}eW@#D3-rXCe`kY-odGbrgSp@B#oFF-@d0d;7*jv#L!2V6~O6S~|Llnd}Og z^^*CDoI=qD#di{Es!>w;Q^4yHwQHf9anUD@+sj0~?Hw=&YGYCYfzRem|F`TVQhkkM zPW2VW76+H(9lWAUPQfc55{|&>J|%FeMKtI3dD1PL313VCd-?X{fQP2omeR#&6nQFaTp&5nR!E z`5#WXXtkK29KAbU(K2YG(bLg1hP8_E8Pi+(=1wsn%P;{iL$lakH?>720YA_A3>I&E z2QCYUIo;0SaVOw^iY)CHa9vIntm^Uax_ZxPi~J2>v+Vq$rfl6b`xBRJ|IWD;IU32F zHlK9nOftq<=jsH;UD7zGI)KjJ>5N|%xtp0%}OwiM|3j1<$E-FQzkE{lwpuWaL(epA6 z{opusKziIi?fxZsDa7hclv~2KbashMSuzg#d&6V3hq)yMF9-R7%hcQb29o#5M?X*Eld!$Uz=dwgr zxpdhfG=!0mgzu@0SvsiHe(E-?06Rl!OVnAEr$y}6>XPQHhZL5%>O;DO`e<}y)WejR zRAa7|th+0eya{!K&w}P$$+|}?EeDFKs-hX*9UZZGvPWNidtA<(DX5Snu!fib30^0d z6fXr{@i^q>^|oKczgbY3AyRY2j54tvUUi`Oa-sUkVq4niS5UB;da1#=jzFq2TW;Vf ze^4aqRBm@hzLL?owzj*hUnF+}vZRLeqDevY$=2?0g_x7Lv&MJ&a@(Km1(K!X<7yR+ zi|_Yj-{I_7bY6#}x-b=%T1_q@0arlUxG5vE2ToUN{v*^!`s1#qslx2Vwr#iPtM??~ z1l;4C?ODjk=h;SqwVu-ceU62NF`o|T-ZhL%7XA7V4NV7U0yRy6xgG~kiC=iVn`I4!jNlxIIwKQKL`vSx8G5esm!d;5s zTH{HQ50>rkUwvT5{kvqugHQ6JiOelu;vVL}5mG#NkJp0kGArQ(5;RscG>VqDNIDYA z4f$9zu)g6&pwsGHkR6vuJ;*EW<~!f-bdDP%3Fpn7%2#^a5G{gqYvP9%o*_r&%X zfI7?FRDaPs0`dsGi`&RM54q47<@nuGhGB{9ft#+yLuHxNV7_!u=L(;}v5Yns?yzow z>rvty3aDm50#nEZ&zp z@nr6n>0W1mp^~?`J?@HZ#_hpx&Q`P8O1arMlfZ^_J%g&qLvdmh45dx{U+F)!c`t(* z4(a+=?hb#bv5%ig4>M(InSphl2pw71C$-eF(nYa*8s7|LRUu#R zO~9OZ+7b(o5|k572gQ`cuY_vpG>!>AHIwRjWH}P-zIvX~j%UG;ppbN_ilDcbRxi4` z^;toT2w~=>o839;Vg)AU^VB3y4fNTvsxx$Yw>1P29cJYv?{m5KYLd}8vW=w{haVRw zRl`Kc3^E&d78+G2@tgK|6BBUt8siqm^Am;jwKR$aw1LZ#CWj%KDmo#zOK69rx|mK8iUmc-7X!yn3jB5Babciy7-`TxwoB6^TXiTlZLb3^e z54pC!xuQBJ*y;P}Y4GqcDnb6_aZb(!#{dBqvC%joCslewlBtcW5D(;~T-lw3n3IQZ zE{n`^28TC=mw4Exe%Fpc-lk|>+-}*Kp**lRo znbUjz=X&#&hJ^uiSgUgg-8OQp#&fW&PU|LoC7s9IBz1ry+mD5K&_0wvXx>khG4a&l zCWiGcf@(KOmfz0G=i9^POk0q(EWZ&a-?&i#l>?PoXC$3GRU!bTlBDX@P zf_#cbE^8eWV4mxY8um}A0aM6r%jp4nvm4;d;pL}`4W2SNcWBrcMeWJR<`;zwd_rNi z#|MaA`mA!urN?97!hDiZgp`I?sNI8y~hKuOU1lE#6F z`z6aq1*I4wANntR51;`u{+Na6iObi3C{v5Y21$V|9&h&PTyB*Jq_0y>t zs{F-;Gy@uRN;)Q@HE4{f6wz25z}D>X1uxj~(AF{O3Ydb0)V%tRVoBX-mE0l;>&#O9 z%?MK7j_+tB7@Wh)V8`DN4CmVGCdZ-eS~2TP4`R7e53UBMFi;OF3DIcMn}XmA=G4R; zW9trCa4{kYWjF?#jHt*b$85bE+qZ@=O+$Q4?x%!5U z{=@>(wZCvuTOhKe@cHIl*(N4OYgnQTxgq5x2ZtbC5`_z4>R=tu@rM9`7 zGU_-|Xk}ICqpoE4Deb!S`zt6`Q2UY*`(wd$O%r~&jzzK_NINxRR_k1Y#c%SUhB_;L z?)Q7@*d4Y`d9%KirM_Ju457i=7XPpL_lHwak^mGW^rHv9)*8{<0HB=3VG#=UwU(CP zWFigeDrmX7`WpzZji&5T0}{}cE33-_$Wpb~CmflK1Eg^$^?*@Q!wc_P2Ok{I;>iJd zjhdzAR@yQ;P9FIq4SB8f2i>hh{Lx`m^4|(ZVXy{yCXmeX!VkIXw( z%s56l&7P`H1Z{*4(EHlE{D{N5MRD%4ZTMZ46pgy~o;I5mG!?MVxXA5~Wp+ z6X^D)6+zzJp>??9>dNUgyK_KD!$WpMHeRutCF+C}i!P%|*a4uLYMhss$?Yt|xrv2d zQg}s?mf7ZhhcpVOZW(BK97Gs1@fLTPqngTd%`>o{M1#k5GL3;|V#{@OqfF@0ZA&8= zl!R#M;4n}R&^=@061Y5RvW(On-SU1gISpeVfw@%OQn&GV3eKl1W<^lW3CB0O`kZe! zC3;nugiv!mT9xMcB__<|SXn=j56G8#0wVNxI+rqlmo69kIQdpj+ZRkAO(w2OJFnw7 zjk;E7;Z1J5-cz&DaM*<@|NUn7^V)A9v;ylwd)DVc;#4LKQ0!qJNWNv9#nJA9KGDM(rfMjy~o}$@t;^*i%a>&i{apzlwT3iKn<_MkLO$~EuGy|}u4(ppwRH;D zu7Y=~7MDj(1S>$*za$^GvH_1~mA|iK_8C}g?yqVX*6I+St%`_H2$%H02N^z!iIc`>| zA2r$t7yO!gXxkJGidg620p4xg6IgpP9DGa~{^gyC;Fnn_ zZCbhskk^Z#uPMyFwe-YI@6cy#zOYfN;BNac))LjE&glK5<4AGrBvC@k!csTlH`BuT&e(Ta?vtkT*Kbo}Zs@4+M zkG4Lb$YxDovWxsugq&YWS6VtG#_44#H#eIv!_aW!U8oST7G>$Ykh;jZ!v!y=P-(jm zrN35cjET@%=Fr=9snCnkhPxQ|vt~(MQ%~He1AKfbK=KUO1BG97*ko~;gBVnhjVaEg zF1V(b5KjYW3JT)Oh28A*au99(wHX0D{_qzByjGKucEyM{~5KD{vM z)c{kOGQ%EAU93Kfx{`gy2TfF&j?ppF9^w&cYbuItH3xof@j&8l;b$>1xxKr6thziY zE%-bYYZf>o^TC*n&jm#Jaju1Jnij`Hr%67uj6Ck_>=NiSW5F5r1v_JKaBw&r+V-@# zoGBwmN6>X?fBo)FfE9Ys=l9~Vbi0ay;!_pa;7I0X%ac`><_DiOpL?{FkygOKR5)tdE4h?2?RRqPm@ix4L;5^$z^|Xbr32Q~+ zjJ&wtES9`Rk-EkcyXJlf2Y;?YIJgbfloSs=|I19Oi}y}CFR*vooL!Sp8c+$C8(nF7 zq*{p_+KRyEW2WTpUg@eTY*Z|(Dv;;|LtT!Z!2>ZMkUey7BNBmj4iTYiAVgs`LZweh z0T<*so?OS0(U{LIevaR07rnq833sB76dC!5@8sMNUsLv}x{@Ps<|eM=T^KiiZAeG` zAc1<^f1q!Czw2`(Ri?gEH*WAcRJ~S@lcZ!Lh^Vc(ZFF+^i{EpaYkF1?!;pMb>V#4K zhMEkj*1{8O3t9sZ!bT;&RYDr{FWBMVX`P^EhJXt#XC9RzWkEGsweb(^4~`cx_Rmjn z+^qi%>i1u_ zPoOO<3LX$E_4ykvqD5W8x_9|Zgo9f*o!qDL$A;}hX6Sx%A_H8OkU!@#ai4iYIFbn| zTC$$uajMz&F1UZ4zrp#Y$QHd&{VBuLAb*2g{TKJwoIxdR74!@fGX@n4|KUhIW#PL+ z(`=)dd0_VK1>7&biiJbMLkqq}UaSA~)b7IklCmCex3OWpb7nyJnXjfkn+A1k^s?~Y zez`j1-_Sj~SOcuTs{A`4&RV}nKv`ml3kW0mllRMyS4hziL14#u2ups;-?9ctK~-7s zxXvu{<3??#KPE7*4>T&I;&2!zsy$6cQhi#=eR)h;XFA9C=7wO zFuCmbLCWn6AzFjVwH>h(`&@Rz<1wZ7^W09AnP)Aa!v+^O@5L3;1hdNg6}EF1==#u! z#-d#hKe<3x>UnDKlX9=aFCD@2&?%MtN7xZ~8r(-$E3w?UB$CoH#dmPDh9u?(W{I4d z5-qK^UD2A6&L3Qb9R!H` z1yCKYG?<(;U{%-rjRzgi=50g3?V=0cCGK^0HsH9LB4GAWwLfi}yy-&toD}=?Cf(I` z^Zh&#UR3L6lC0j70ILwdud<-M#u7$TT?##+T7DsR8ogfhO_XodAtn9S>)63brj!iX@3TzyCHjDkGQ5gO-Ah z2M+_g06V)L)j4!s*%*+QZ)&>R@9YwKPUW%+NrPUsxmdM^xl4(q_SO};x9sg3Z%aCJ znB$>qSKmQQ*hR$lV;)}e(0Cn(n$(FSOI>eA3ZPZq~@PsTy13%6XwO6 zbKG27dc56?)Mx(NzjUX?8>Mts>Ttg0gY)LgJcqEC#ZWHI7-4UTLM$KOaqUe&OkoL4i5d*LQQi zL+AOpnrKpj(0LsZ8W9$8N?GZMSW7w@R0q=I3Dkp%-JP4>0_`=&c~q=6-%_#i{jf+TpkRepV|5 zwQT6sP8{{23+{)_g0djj?K24b%~2K8=a z51?ov=5(GLV__q^VA4hl>CWL*s4|ieuxaB*>d!4Cz$P_R1v}EcAcduNkjg?t`&U1R z-Qh}h3zQ{jbkKNtkDjC3W5bAZIggUBW_-MsETy*UT6v-$0_~n%8&zkZs_|mcvZwUh z4JY0gm1ZGYaAi|kG&U6Q@wFH~RV|?*s9wMNwmqCY=ObvV@%gH)f+4--C?Uxo>bIip zDqGaQTyebe44&vjYuH+0QI!oxn@#jIt7QkaF>NiGxBTwROb}b<27}!JeiKotvVpFFiIp9{{2*li<$*9&Gp@%6 zwZibfeVVXVV*whYcm|)gA?Xn@{``A=q`WJ4v#7ans!?4(4>XTO#{o?|77}~fht^n9 z_v6-r&sHtE?>^e@OfiVNm52O=80AY~1`+p@ZO-$Ncs?$eRNb8;^|@|8+}jW`7C#5I zOc-le4Ie9as^D*PcK0qVh+%A&S|>k_`0N*^81ipo6hWh|vz0T~TJ*FUbC$Qn6#RQ! zn~-aP7Q<$irkQl5z5p5StDy7yQFpgX0q9a%`i*>xD@5m_599psR+F^*@u2hSeU061 zCuXZy3D|#F(_^jptM{`iImgoh+L8*UByK|I!83aGR=30xL&=nF+7~~v z&OCwLNL0+|<8WMPp%-YB%4;)6-%&tHrqU<^g%3J=aAPxkvVb>Aa(`kHx`husfa*u; zvfwwaN7LYm1rzky7sD$%daW6Je~7t(r@Nd(-8VVQ5V&7JLVMm?|L2atG#{ep!=MC1 z$2#Jw_jOM|he)m}*8RAptq_NpY`t#R?&{e%I}39Ju0>6{k!mySnB7RA;YSC9;KNN% z0H#Q;;`G!%Q&LF_My`=TEd5~v>rU&BZ|2gI5zriy9>=CfX7 zqtl$*XErGvP0$5Ar5d9?EZa_Codw$uwz&{W)@^ZPc&(3&k(5KSrSmX4e@Yqg=kN)7 zd$PceyD@ZbprLd|eu`sBi21NfNTa{SSyEqmnoMo$HAD*Jc(VyeyuAyV2L!tp#}JeH z2=^I=NichQtj2u~n(LL3s@r^X9~fA)WA$x2Nvlh;qgIv{yuenWC2uUie!DA`Bv!yjoQg$fHg zhoo#u(tw@-&rmk0oU+!OxSN3B+uRsQ44r}5aEr+_<6TZz?MeC)jlkoTlZuqX<}-wQ zbL3OhA*0LoMhzl*A1WGF+I;PnTY;`If&Nh>3)Oc&>t~efN)prlvl2zlt_~4p(J!}} zj!Y;DEEz%1cfqpnz4QSqv?4*=!ZTakL2FmrVO7IT8S?3^m-m;s_ z$z+5mTQsDLQe$kGdZ#7H@Eh$8BhxONZWppB#c4nBoOo3@NiEOLIhdPUgU8AQ&mlMlE~nZd`7(5Mi$>zjb?MW28RIL> zzkP|?v{D4TJ&*1SMiF(RD=4AT+|VA$pz#T((-X!K?@U03uAY`Fe%MZOQvnz~I^VyH znXXz_FFOG&=?pKxKN64QWYM5)(yA|2Hgym@s(mk(uX-?2K-9;d(7#~Y31jKe%%gU) z|MqOnVv8k3JsZ%pdZ!(+Ms1u00B0HO-Y#?XDjqCJo;MbPc76)wsDtJ5s;P-e)3C%R zw{h?9CQ1#V4FaUbc(lYnO z^|5>6(4bE6T(`Ki@x$eCWLxc^rsu@bWI?XOf!a}?XBa=RIwZor)&$KIhJ10}#y$hh*Qr#iS#Pfn%}hU1Mo$ zj;8(=P4@UQ^Ql;Et{VhTQz}Lt9T?2R(LOoo;AjH&UW`fArs7&;p8kUKsAHh~k zFn(vb)y&^RiN_rVBV^gJxJ1=WdNsdQcB7D>1HU0q&dVOR|E zcDkS?MKd|Y)QuNXd_-%n?U6TOmPT*A6=aEB2&8^Cf19_kA0XpoTVCjHKKgoA)$rb9%)+GNRs(+$36nx%@5>}MzIkd#g`aqD@J+4+oAJth?aqB95%K5A$W+=m6B^?F z(P)QsEi3|B1HJO5`K-@5TZbpT`-jgWNw(Z-83w165$0vaH)NJI`mS`6p?{5V3Ox+3 zp6;c!fu*-8TcHoEqi zgJ)@I{+`G=E}h}w2^ayNrKvwgc`QG+7PX<+fAG)%H;BlOCuL@eaB30pn)?SaUwGAK z2^duXjg0ox{h3I3pB_CW_>>9R{kdhdgE?TvBGRByb`Ps0M}W9&!VJx4$KzxH!}akA z$K5ZU4j!e<0j;bX8Da&y!RCHFR`+FLtcDX+WhL)_4{_hjf&D-;{$q+e^X?N2J5&zj zNThshUm1}|2=VqN9n!3>a4&ZJUCX?Wo^Zvv>umEU-?w$-msqCUMkC@ zOxNzb%y=AL8e<|N=S;-%=ov{Np#z7CQjdn>zN6{Qkmipcib&$JEKeFj3r=C@5l->q zt|^4bQPgObD9Uu}zFZFYdsq8Qz+)dv#IX56^L7+&4C2^br;x4SOjl&GJ0;iseNP7C zsrBPt>ym04Pokib$%$UwWpi~VRY7g<-4fgE5foIq-rwl=TEHhW8V&gQAv3iCh4e|E zXhJ0?FX4B04mL#Q`stKM!|opH`N&=X9d7OR8nc^p+=d`tuuo9(J%qTma>B zZ8BYi@ddY1QdKxz{b+4OSz~!TM->u2>r2ASl(ok_G0|(!l%?c=l@)c+ z#086XRgcwCOR z-DXq`;aI;%X!iG19M=FNbRXshm3 z|D7z=e>QBKtakz83&iC{y1WnsE4;|LH|PTeBV(*FMO=uWpU-+) zy~%Z?bYwUp16h!uZwF*9O!i0hcscdNrW0zwL8Cq%U&QtkNv($MlFIs-^zdrux%&MS za&i-g2=t1IJSR#hq=TD(nIA|Vr50q{9|Dt(%?uSXTXT944pVzNRF+7;H#msHpP{M+3u%X;v(?^EjF>wlIAMp%JCE58@U zb*EaiN^A@3U3QEK%_nbtRpEy#ebsb^mU97LoVO2O9DkC2*vhN?ru;7!>(t#?GW>_= z^?c1iHpSj5-Yt9L?U-iLQ;H}x`~VUI3uJj%mBCPmq1RE+0h-GeVX!c5p-PdYnjxz+ zGj$$Vl~B^vev@yGwrrY`;xC5n5wAZzb6yXenfX-i@yg=wi>~c*zw?hp{<)Vr&itRs z|NcLdto_eLO}L;yIF!FK|4Kvz&+)wfNp@!ctw8VlPd2>IVz0+Ex3HX{r)sD;O?%Bx zL-U^i61#!g0R3OpYSWlex^Cq<{LRn0i^~5G=l}g??SBoo{eObf!9?@8f=~`58j}0D9n-l-u z#%ay(@lH;u=1u>7)u#!$9(XRk^8=zI}4G5R$D$>U!6| zVP|O(*W?PbtlMty5KT>WtIm#UYA9D#lKE31+`hAluZoI}4qfb*zHTUFi`oUpQZp*o zgXema=ZwVXoK8r{hc~oF;2)!aD|+s?EcFz|nhznnRZ?AD)UCOw<8jIFk`e)@J>gEs z)Zf9_zba@K$E*FWqO30bG!dDf4*Wjh;j5Be_^lvLuk1GUpB7Y)6Gzk^R1>JFojnKc z57BYnykaYe`-eO~Mf^h5P^7cu0{k4)%&vLq>3T%@5A}+bJS#A!bnahWmFPP9Lmf{( z{{8)nii+x_&p&PY<(Kr&cl-DMUryk^dJU_|FY|^^a2SF?dovCi=H@0Ptpmby>#P{= zcRg$Ze}oBVhEfz2#07b2)i!Xw%)Xd5fQ^We;&o8{Gr4*@i@*qGO_k66Cl>IvRARbZ zSoO0DsCGfUbfQl7I@ScIF#Mmc_&OP&F^WLXi(yYzqeWEmFfdCB$2dd&beCG$hvU-y z9LL;Ba?tQL!6%NdIt3SJctsa6G0b%S@VyZv{QfM{5TBKF$v@);*I0zc$hmRqehi;K znkK|4tVgGW9rc|e=;jT-Sw?`7AJ6mk$(5nqpfenk0JZ=T7lm*RO`gYo_ zb^O&%>=6chci+nNF6&c9*Hr3m(vZ-7-6O6a>?td{Y zX4LXxClkEd-+>hie=luazC;!LTzLt3|wp*~Y z6qg5!t=zVI{X@|22o5HY{-=jT@kTTMT8$aP|C)1 z-cmRv9T@PhyXK7u(Z+7drucWTf&w}Jp2Pnd%U@HhrH?N=RV5_Q{&5g?X1~Ax^R&c@ z7^8i56r1^%pk5K*e{(z5gw`{07|i>)^hyVYhOD20iv>wm|LqvV?d|Q;?uJBa>WvjA zz|QIU26Usg?#ie==D*FItISiwhd@r+F0Rsb_<*p7ypg1ZM^u!5Md?I_9#nM)*;P_f z^oHF^#12RXLppCS(sgyG0Lu=0>)x>gPCaygdyt||CkGsqTYr-eHG}(w1Zi>#xM3~? z)t_c&LNyijhV|VoinZ+2Fh~_%$Y^|1SJzn^TYUu@ZMo;>)5w#^H~Y$v6Hh?#M=^a5 zB)_(;9}XolK~#tl{1&)q_s(;Rufn}&BI?=GfPp>e_7gK^A+cex%zOq<6)$nj_X^nG zKfiQ<%^YX9e_!s=z`9d#7Btq+o!Lc$>NpjzMyQP-ruqzw@hliL^Z8NZRX4t;A0kY< zIrSi;q7>tD?o@${^ydkW_RAcTuV1=~UGmhn2~I?Q$EFK8(2Dk)zvm~L742sFYEI}m zt5&qh0kuaCPvdUa>YtLjYoL`U<&VqcxH=~mVB#oOSZz>DgCWgsr`>Ul4e*(YRBsHI zI_~iPV@+HjOXPC(PVWs-F$9|o!5n^#(weJnNrZ?r+*0=6CUkwHIM0{+v{(Kg*4{EK z$}f5w9YjH-6a=KDrIi>O1O$|nhM~K=8wBYPknYZ*bLf^Bx>LHl`wS|-|9jr|oa;It z&U}2HnZ5Vyz1OqWUhBT^XSTtPl#fIjXY6Gt--J>|g(^~RQQlD5g{po6=HqI6Zh!CA z)qekG#dwPFHvS16GImhgYKc<2VI#^9-ihwxVTP3gnBazh8GXyEOF#6@jFJ4H{$z%) zxN0Z}tz~hZEt;lw-yRLyvb-)E(3@KG(;g_eI%+Z6{?&krGOzMvOkUw97OSej=Xedd z&n)ty`nf5SdAXx+_sAB<*Jx3}k}q-pn?LB@w23C``U(-(?U~ZF*1Mnun_rSDs`;%e zboWRZ7O23N(P^aAyi%dDj|JD?oz0r>ixz1FuF04l{1H*}3VR(pW|lyfp!4IVwj*Vt)P|0gc~f0uumY=8CsO+(@{0*08j z#rMi|PbR1`T2eDArtzX8V#Oa9FqQel)e{y$ePU?P=@1%P3VazZ(>j4!!~11yN4)l} z^cptfakQeM5i8!~{;`C`J3Dw$T(cxW2c7jYB zn20IXXM2COG#nHA?*>3MAsMA)QZNcGCZ$9A*Y6cc}Jr%0mf==*gH&uF#9*BQOCaz+oB z5Xs{V0aBT5>6)+Hci9vRz2p@X{>Kk-e(8Ern4jNcuTJDBi0OD0R7v5)#PV@L&o|_a z)!Ha^B$8t62q4!oDuM;kS(C=7vrwC>kYM`0FnCF?{;9d1+lqatcFLJ z1SE4)!5F&*Z9x--t`{+d1qo>Xr_RRDwZ>#YSUCv?hh!+4oOm9hWs%SN)+DRM@mF6K zG8LkjKNFk#a-F$|O{f~6bjd7JeT~$>nrK_i)X6${?UR_ zYx#2C1`PFPqMksf{eJPogO{sAs9gQ5suu5H{F?ti8P{89HEusl-Q}Xff~u5Srs7wQ zww2jnxWZS`n|WVQr6eea`LsVegP0!O(*}0vynkKk`Fj)Rv8M|Qqg>GyuQXYQA8Dlu z^-EV$9*1q~T}alLvK6I4DJfXz=lOnoeJF33Ef!G@S2kIUFdYAJQjJ5hVb!NV8wX0! zT@*K-A&)VUeJJuFob0E{-<#=wjOnZaTPOJNfoV6(7}lj`h>k$_0~& z!w*=+%9SHQ$env+9tEcVz`Q=+xLnLfKL8t^+QI&K$fMb%);+8`b{uaQ4y0l3mw}kGDw9EscO#UqL$Wz6Y;K z-wBY!WSdW~eV0WM#76Sh{RJ=fj7m1^TZ=;@J0&oJp>1fKsqhlMApTVOeM#9Z9JbI1 zt(b46RcB(6XfXnMfV<|D^$hR*`E&y69N+E5?Pes^<8yNMCjsP@17@Uw^^yK0zOF6W z`&S6w70xl7#WkzN9m?mtxyT{*9Hapyh83+zsU1@%+@!R84N_z)TYn zPrg7M-kdC1bAZoG2TgO!UHo&t%0|R$8yWMkv6lNxqL)1V-^oA5o1bdxAzs{K)>%X2 zS&GmXh$tV+R<3m2K(;s^Y%g#nqk8w|ejnX`yFq@jK?A zYeQ#Ni$xw4;Egy$SZ4TAiq|M49JXY8 zC>g%eD!S_!0`a}M)-#`r{e>eByYl^@W}21l0X56Dia6IfH#e6ie&wHMZT4sRIm2M5 zcc5zNFD(C#CjzhOMFajAeVz37W4maK|bZ+{ALD*6&mPI6|9Xg2WZaZ}F?Aufjfv z0^=$+DUv=?^uI+qA!vBz;Jfpqd)=Jtwm>Spp#7C&fKzY_dUZMY*b;_6qL zFW98tueGoTkXpI9W)GV$2KU_tbVO!zAFR-neURpIJnREVto8Se;gxMKzMScA=|!|e#a z#>>l)>hDr~9Q$#KhI~d##l=mHtgw=ZBx^-}1=Iv|T`^M|}@z;6>T5RSIH3$n?+4F4@s%*YR-=v)ap90_+saRDhK*PGNp+Q z2oE>|CMi`do84V75~`TC+M{}H_4@^*eqHr|y!jZmjggy^>6vT7M63M_aApSo{L;Q2 z8)#Eb)D_>bKV5yZJ6jc8z>TK5s$jNGG`t03nVP|`o2iqZImXo*tEh|mzL(Eabt^7m z-&w#QTOAwC7lL0FLT^|cybEE8+sBG2nd_bM%2OL-QQ3o2R5iI)Nevo@Y*%L{(GTvV zj)gX~=7WVSFLp;9-_|Y-WuETpSTq9ha|5zt2BNXX8 zutrSXG}RpZ`ms&X1+c-yRCStaoG$U>cBNdHT-)<&cZ2g3rV4j>y{dNI*~G8y5$rci zTZXrLf-KEU<{XjIPEqRahSsjJv?h0wP$Hqv*Y>WCGOYq|?ZPs(RK@h=6dUh`hJ%!- zr1?k?jFjxOZxMzMCXXW9eO4|vr?rnx%xiSKX zC}7p5;V?m7%}I8SNaMaN@@lUWRijCs-T_uW-stvm`GwUCe8RnQxa zDPEv3tD^ZZRsTvNghxC(+po)4VzP!dA%1T$WTr~1V9ZQjyIlUuEs>Z%RA7>T$F$n) zHe>fy-k#yOr79C^Bk8Gu0JI8hhBtX&cn$iYzkZTUx&LQW|(4VQHaRK|46{USS)VzkPcfpht&T@_auZsl>T_1Ky2 z=+&3O@2Bd$@v6;KRH7>GaEC4o)kuAz3Y9zztfumjPzCUPreR|jki$}?t8D6C95_?e zZI$Pq!EzmJs9PQj%-Kfq?RJm5nh0Vc`XLT1R$SJet~ZIVUqVeGLoFDqS)OQG-Wo3` z5??l%IG7r}IAX#(f0OXcn48V80i~3B#MUjrzFNs)<_J1IXMg?Pv}aPqM7XyS$f{x@ zqu99zVuk@Ua3$1zI~^2mVdduW=`-f6?wqE)&FDN2MPQ>KuEg03VcPBfk~!YYQj`oW zwRmn|eO&;wwA!5m7sy4Mp(SDp@}1lptTJGd2qU-WmD(-lYSS+^yb3TaXOI|=VNuzR zp2_Keu(`fq+e1gy+p_0JFmgUAxGya2o=W&>;YvFW4(ir-lK^g;luhvhSGW6d@p{?j z*tUFu115MjfmeIJQk*kPndWZz?o@`>1e8F7w?0EF+=-Kkg5&#FKoc{Y`FTtKyai$j zR79rXlaZ=4l#DNdx@FWx#_y(T_9%TN;?`0Kt5S}#6-Ekt?bs?+9U`Gl)+XCCjk|)T zRZ`2e^F3EpRc`TME~C=D%+rZ!eYY>PGkz>ZE_3TfZVt;E-^cTgr=YWJ7BO15REgvDpM1W} zkibo-qzr)>xUjV1@L6A`R*3&D-jr)!HDo-57zJEt7i3qw@B?TvSJRjowJV6%^D^Pc zu4^NE+x&QO@PH<=vplc!w2~e+WOR~{8NbOwu2N+O?ee{|-dNL~Q44a+Qe~V3c>7+v z>N(!CUY5cjXiMW^-w&2KUPYyIg`UYB^wVvng|@Ssrb_w|Q?S`t$Q8Y&2lxxEiA1~^ zuD0y(JP6bGwvrND$ZDkAc{AF@=wBgak~8<{@BoAzhS;)|p>7=*zrc?dQ%6{RBo)QW zCU@f=GcS@U&YQrzq53JWF`?!zyXMc!8G3k7hiqNDgc-yoi3#5w0 zC5RvI{-)G3Ugc<;?#T5RAL<4%(VfX>DN?Q|Jl?ky%D&OsMiroe&j?tq1$E}JDwCpzq)#})yjFlWsP7S6~6%a4s38>2e&PD51!gkx6>s;Y_Kq1>KHVb|E2olq<>_#ivo*Ng0mH53-rr5 z%=eP9^eCc#1`K`0+786UEegY?jU1$syX3POW8 zXmNA3OS^>LNLH|IuU)qxvjhvKUP-^t`Z&-*l)uNsFoq<=Qz?LTmCl|NVsB!hHaiDV zjQcMA*J@FCq0ID*ylK%lJtp9ZDHu(m7tlLOcac7zOc!1L(eVA7|4N+AtRqI1stjS> z+H7^!gP%V4zAWfad!Fz*w+=0Qz1<+z!tyh$K;EVKewN40C>`oxzh+tUZ%dk+b0#UM z2~ziny^x@ky%x#jkIWa|Tv^G?&-)N(LzPhXvZ{tes~pg4&l??G(&s?R!g0Q($C7@9 z)eP#*NZABsCt6_rE!|IOA{~pP0^^VfBE0+6mIndo`n^R8LK)6!Hzo= zCF%EL&lIEiBm@ex;i9|ynd3DN9bNHq(k|^i6O=8!5GyKvS?kdKxBtGvbf95up^6NM z%-ZI1mnKtw=ISoNgdxjO;suMdabFQ0i<|ijg2M5Z;%SEj0RzM9CpCGs{&UWCqJhZA z|02wfw4yJL)jG@>ik9rdT1tE*DHEcL@!+Xv;uU~!(G6RQ;|$nAAzO+7HhP$P2Kt8L zw@2nnQD)8CfhG$>E*o-CpsyqM1>}WPf^iKut?e`fvO0 zepQfJSz8>rJEBauS3xeFgt{nksuTo|;Uz2Diq~*;&F0B>A2ArHM&}3H@hH>T>`xE( zsp@E!WCcru=>{YRe#vfaV1}A3Z2TGoSTTC%bF-bcUZxvA9{DNP+n$YMo9Hc2< z9?++S73?4ijA>uh<-h#vewsgL#9y_2*9@_@?~Jp#b8W^w2xzYMbQWBDiOfHAUOe3=W8VukE#WIYlUj@ZE$O-HOjW)%g%D{gE zs_+&D+wYAP2xhR<`VUHe7Mk3}ty2qTjauzkjTenpEI}?%mYVKZLEcV|-dG{fxVO|| zM9Z_quCtbN+B8y*;*`m@vLjyGYSj_7v!^l~m5Yg@tzh8v{wyyq4a%khgnlQs9gr>P zg26Jk*$E`=#+&%=EZLjjYr|}szA&ek0Sm)oI4Ap|i0i9pm%F*de^I621u2;+bDU`b{eTnthbGIZCh;sr%sW2?vU)5k_fhijli%sBU}GdEbj1X+{-{x`X*Ct;O>FID7_U_2AFx zMjt$QXUu*zNO8Mc6#DG$u$|srN{2d@lKfr!kAAGRmV@qS9aUVC+p8)$k!$2B)I&(# z&MwbBC_|4a(fpvfj5@M$`3h;|Znxf7*f}v}+}BPV_*p$59kpIndP;xn;^svwf18gB zxn8896D4_Y9d&Gq5tf%(W=*aRpG4V{m#MB+&F=Jd;^XHmu5=vGj`_+?S8(UjmH6DF zxoO5JEEJ<@>Qr)60l+MZ>!k>vx)%z?z zekY;)wE4s436_C<(LGgDqxI`9{n#5B>44tv7BzcSFuVB65oqKgr7%yEom+t(j>3FdNKQ&JgLvY#ug5ajR*>(R*mwke=R-wFwgq zfviUi!p%|Rbda8;x7G-KiIbs!G{L;7Ecfg+#h2=Cy9H?Pjb9&a z`JTM^?VA}%X+BvHsB*bgSKN5D@O_A3{Ds_b&|wDyOkflF6z3@(&F*X2RVpm2M3dp; z{M8PlvHnbQ>Iwd1+Wkq9`||^q{i_5CONtC4XZ=%pxTWS_NXt5Ho z7un==X=Gw>6Q8r(eJUL1?s#i4n-0NKQ2nCm&!!lilyQi5I=q%Cr-Y2uAkY940#1VjuNz2CR(!F{XU96r;%Hq|!Uk^O;4FlW{JP zN2G;Jlys5E!+i|h=YV`DtnvO;7}en!r2((X=?dhv&%fN-T)=vJoz9|~B=>ysTpgaX zWE`hi|LkQG`2PC|OVzHP_bFr*!Vjn+K_c6~{+%;r=b-Z&si9*yvC}Ie_Ivd+%BI?r zEHe)BgXkDu51LeW|E1q5w0vTN2n>$v$IoP2f!XA5TmDYihHzIXf&JRCk*ZJ~7&n@= zVft!fZCNDjhxu40t3xj)CJZF^F-PV#vC{o6zlq7N>SC@Cx-cu+vS{m*T8^2>z&^ns z474o8StG*KaDB?vSTb56>C$hOwcG-aSOQx{6LURiNg*LiLmBwTn0L`j&A6}l$N)~^ z=t#>8O<&gLBMiwgA0S~L=8LUq>vd_U$oRi3?#72ITm^@0MHWx($x(=Lz}&PWV30oX z(ln`m3Ob-1r6B{RVgs5D#bF8|XU_s@?rxW-V^!9KkZL#o#XPWWwqcxIlvW3o+3!_>UVVUpI zf1i4u-f2Vf1DT3oL3|}ZQs6!oF3(`rl7KU-pWOPFfS8n^T4OAdehfY~HLGM*smXMA z!F2e~^WV#3XbZ`x8DOr!)EZ6Wy%7z`SPvamjIveUCka1Ga(DGiQmK5(iDzd0(SG_C zrB3SgyLnvIadk4$4EftRzb21v%=kur2jX+?AEOIkfxt|c%f4wnnEUhp6|*Oe$(mdB zd;o;xcr^#5*8=jj){?WfZ2ham_tkOC*U7JN8ZT(n`_W_T4MBI+8i{ssXM+vi`BKT! z18&-*9s3@%z%Z&EzU$=4dL33bRn^bAO*uVwTe{0@jKJ(o@>1k|LdTmC#-bR&c3@4! zMi;LOP&hu3d<^ov69N17PWcs$)?efIl&8gcnT~+{aedKISn8zi0(y-4%Rm)BRt7t}bT0@>>Z z8KTM)+P}++!xX+-V=3je%H12%JPwf!h1HLeCk6V%7r7;u)KS){?zG=?H}S$(~X&7b|V zZJ6oY4iAfyVW%u>{L>VXZyOqy-=X{5rBhYGCkfR@H@EC#mlR{;rdwd<6Oub{`Pv?8 zF`u+zu;6iF#c}}Fui`%|1OUWlMjp5zU2wZg^E1HJ5WQYs=j1p{N=*7vJtB5owmm{y zcfnuHeK*UK9CMZuwdD^#xLNT(ht3N4AZWG*`InrnL_8%JZQO3x8=`3GM%^=j6yi_Q z*GrjHymX;>&m~zDadpI(Qh$JM^=2y`kE*e}@(Azm2tLj@O1fSNMcC%|=Xp`QJY!&_ zB?U|*ltpNJ<+6DJ$41{x68t670sw}HXfXwHJrCUhf_iO+jks#f-+W{nG&b3s7fY2` zrTFf~-SBw!;Bs_y9$}2gl(mzAVH(Er<)8j`&X6u}QYG>N#))U&?9fSRz$7I8dx10v!v`X@ zUhzM;tU)120|NF0Ee+)-?Z({%1?is&$+Cr`7+oD$e!@;hBeH>xL(3x8k@j6UTt~zI z{x4wGAHvnVuG! zN$H#aa7iD}w*thTV^Q-Js+aw);=-YGJh)AS6N~8-ExdiEo|Xm|K?xSHq3{ZqPE(h- z4uBVIc#7Ka6mE@1EjmYan&)jZ_pf1+G)#h*GHgr}v@DcBny-WGgk0q&A2ihGZ1k=c zPn*o7B8`#|xe3lFq#`q<6w{sbW zj*YAx&3oPY4ImUg4pnja?LEQE1tWaGqLxa_xr}1MpBUz&tvEy!DZE$aGQaC_<_Wsw z4gFaTx9@LPcy^JtmFBf(eFoH>bs||zjgF^S6$+OS=p1dJvVxVH950BZhf!#g5X4Vy zIQm0ew#s9&!~4kd*ta}-LJAb!qRC6?rO0QO2a2z}CTai+U=EuFv4NS;BltAI9LuZ@ znI)`MwfgPUQ3=wYKEk3AyzU#{=|e$;P%jih;J5ugKI;w5@;jZb5XG@vjQ&K@zk38= z3BMT*5F>ygr{`6~GGOP0*+hx%>{tpZ33>P1jflC2z+-|}sqsK_xQoNfO0YH{B`%Vh zfAm6$fg~avoww4nZu1gxg36>JoA>&XjF#D2opW$YH52i^daGPFOLv7ig)NxXdhtj) ztwl!=)X%`L%ob0szi8&wZNcDXl%Ti4Cgp^bAg&O10xjnw5YL308NGM2UAP!*s*N=3 zX*#)-g@a;yZeu}>IeStNjT~c2?>q*~d&2wNZJ^eB`{zaT1xRW)iz>3h&&&t9KYp`= zq4MVmk@u~a4$pSx#|CYBMvZLN`tQwd86UHJ=5#!-G?5x8>73sOK5uBK-Q3myHGq}M z-pP@YV@4O@<&&tU`K(i*9IsL8?##)b^vQ0!gMR+3F`0=r>mauTy=8N|K18t@l}jw) z;jg2i{A&48%Yzcn$Xup$*wsoS7OF($ez$;RzA>zPrf(1uLbqyDMTHfYf=xjnnICJw zg$QP$^5ZfY+ESkPB9WF>pEm1XqFhCX1zuCOaG+4_Mwzb}KfYvv(#PKY#QQFgot8AL zo#!hy^x=8M=45zVzbkJ@fnhak8w?JqazE{Qv+?SQrOt=AzwoS7+7E9~p8TG4CT(V_ z_Np{i3)S7aALm}9C>^&Lrm^lP2&=C6faPJCz>>7a-_vd<#p7tX@^h-(fF2e->|A!S zVYDu@0=YUUpfkZsl}v&OrCXD6Y6+|O`grW8Q`JPLqUm1K5FW%FFi4l4vCtwSNTkx_ zb&c@&Y)`zFGMAyVo(c)_Z;0>UpF}Jqa`%#|mCjO}XUM-Us_Nq63M!<9jMMQvbCUxv z4Tq8H@OR4vxkU5DVVb~kmTQoP5}_DImFwA>$c(k4msWZpb2pZkY^UFgK8dofzCcoH z#E~eFP^rS3msm7w)*WdX2#z)h&puC+&Xi_1o$-cvh>V;Zz}CtNVg5fXGdt&?iaWo$@Rd=|fn< zv^83>TYq(3h`vS2!2ZCLe;t$4^_7n@BVAJ2;U%gDvBz2I0FX|`u-2ShXU;@O^q9hK zHrBvn_N?UFbMn9oHuSX~YxgV7Td{kW?e!TJpWev5tKV769U!pn06JpV?E<2TzL<^8 za1<6uBB*vUm_lynY5_+C)2E*-exS&UMXL>h$;fln;yY6Tl8}~^{yx5jA)q(Rr@}O^ zak`w{@d97)`;29_KL$J)V%U)tZ3|C{mxbnx~ z3BI-&(QN5@k!dY8Q_dCsDk~?EYQmouE#$=t zmYz~VX#PItmPF>4FLA@ZDFMY`xEo(6KXVAtBv?hssuklBd4?e%D{BBXZzeNDD~h2ow=>gV7LG6ZcE};9bXT|Kq-HZ2umkib#fM3-ye&zt zBklY~G-_j}Irpw&_=1fXbb8mK4Hk}sg{FQA_lZ8n3b3g3^Pk}7H^e(qX@fsh4u1P8 ztB$$Zhc@5TZ$w_O`hKd4nvJubv7_PipEw|uLiV^K zU}$AvO#Brt_Q}%+#?IWj@d&N#$bM>XntB{-dKcS9moU>%xx$f~^Oy^8cmeym%fr_##dm{?ewaVR1|MB2@PKi2_Zn)! z&j!snshWqkPw9X&sS}u<+b`xUPMrziHk=9b+{efv#+>PSQB4nn0GMg9ByupBUa{JK zc%%4ACk+GJHm4t{qyiKYiP?am~`Lw|C!9xks)FcA(*1``JIc!2(g5PhGR7dT$SuJ94X_Ar1<)q&SoO< zG3>o+=u#F>EE}xOkEDAFkUkEWB&$P{d1yCp;x;x&{w`dq*bruaZ0yG9O8Lp_ibZEY zB=jB-nmSS>9R8)Z;r0nq^J!Zb<5!TnC;*~VS43<&zY%40S%z4nI%@iW;q^3MT3Pud z#ZPaL0c9$rt;u&WgSS=6d8T9>RaT|2x{LD>P=v_q?j~<64~F;xPHNUtF0)0Vfvj%Yq{5Ni6J36P zN5=(QI%m!vaun@G60KXQ{A^m5wNRH=0&E)*o?o=gvG7rK-~p=A$F99sqf=9VYX`Pm zXe?S!zj#OO9yT$9Z@BcBdL9$r!c#PO$Vis1DRzjRt0#XmS zltEc>iT>x}P|iQSJBuDi7Y|}!pRhmJgWBo~7{+JX5#iz9prCk_$BYV6uTKJ&0M4JE zlI?UVQhp=ss=Gvd%_p6`hcW!tjAymdGM+*m zgg&G`Yy;5d{W`KQ?Gyh{R$x^^3|YJ1v#;4meHc0@)dHYv@86(M25B?LJ?~9Ld=nz< zt#0uiZ(~TVc;Xd6M6>!1i+Ar~AkArmm~z4+q55-xx!i&$bYiw0Eb+mdV1?q5q2Hcs z&!48w46XcxE)Eh#@LIETL!|It>Ub@ejkiI1-qBLi_Zq%uvgCbe$Z`bMRZs`POCoX3 z(Bx32LXU7cFS5-WK+xW@q+~w`EKFTY4&Vu)%mIb?-j(xW`Kvlg-he9cG>#TKGbT0> zg?-78K`;@}!5-MD?I`#IEk=4YW-cTZn&7LZQ#Cu-JVhkzu)oXc-bJzyvy^ zt1Oo8iRE&BH0{w5@}AOr5eCKi>)*>+S*ui16r;Ivl#zL%#D8@|r9N|OsGAp=ny}z8W*ZDs zCvv5Ti0-bO>!wTDhHc-w)@woD;jRH5H>0ymVhNyjj<9PYJiXoFN_;5fLHe zbzTBH@MS$~ygsbHhzw}+L%m#I;IXxT`Y^MA=H&i7_g39f2}-hu!)x)O?LI5s$wGks!@u3# zB4ruw4!5U=%Z{$2d3c>i510eiWB5ughN94CD_8hb*lt*LvqIqKM5>e5l`GS zm`!Y3c)TXU#_*ugnyXF;#br&DyiA(VKtVa+v0Ixx^Ee;QX}T??Ft^=V4x%vf5QJ;uCS#z0Enju2KZfp8074BB>`XOBN+*USMAF4&pGdEv) zC5Tuyb=G8VaqZB=oFe6MaD04pU@$A-}y=bir6@lqT3eVx_-xxEz4f}RT`;sH~;GQzSuxk zAy0?)#654FXqsH|mwL;y!rJ+fl30=y*MsTY!|I<=ii0_%;Rn~ne9l*wis!`KqVROv zN(8|5?*>E$rLo7@^CBtcQxGy|%h3sqdK_Lu)3zM}!EX!(>%8X+6GnzYDGG|T-@ZMG z70`-R|!Ry7DsG zPyRse#_3p^*VDr~aW(oXcaPk?sT^Z%)GE{~g~t~N)z2HnYsBPZy|p$2pIvl+n)YZf z;WxmD458G~(lRmwXA5VYogcqI-4g?te@(JMA_?jkCu{z;{R2kTU{Fs5B5>%aY=~7bbswkmNXyr}2cJ5y87<)e`Km{`zD~NS2ByEN6nPYEE(> zk(~gzYr!UI9}_o-wt&;G5TydxY}ELf)kpaj=`rv%y~%c zak-p?&U=5-#+HuYGxDU7SG{^5g~zSOkx>fW=zH*kNvIA4pV)Tkj&2%$&LSqnSCgkt z0UDVqS!%?qv(j=Hx4-;emMum6fnMC>EdkTJZ+Ax`1C69th$Fl&grMMRajVE0G@>_> z11*M5X@eZi3kkjY_#tZx3#bU*qVi(M=jcdGuP~EdLu;*%1D`jC6u@>BFaZG$mS__6 zo)|YidtzJ1U_>|~L8pE=I1GChY~j5A+(v04H60$KQDM}3mT&y57BrGA&hEUkB1oH( zKgKfOsB?H5){_~t)<0GeK| z99LE}lhG16-tLAea$hB?rX_OdU_7{znq*YGm-ckLr4`~^^CD&E&4+hs{8UmY8GP>` z5J=6q2K~nBrsOf5fdTI=Yxo`BuPipQd_?Q_6DrPV#L<$EZV#4Pd7G;J)HQ zn#AQ4dl}bPP;psO)1+>?ec<4)j%#KsPUtvoIJv?{j)6IpKH=7|QY-nMYvvsr&aA+$ZY$n$klR_W;J z@1~_;*j?>IM@I+CPf%v5R-oCvLfZUU2~rbYqlg0SF0XDbFAF{FBlG8FJzHnh7Gqc& zM)Ti)_O(cBWiDSiIx@cz-&UsZR+?!oyTh11FnMPL)+_YY5Qm#)eEW`tZ1)E1ob#!Y zWjQR^QEPwoq>uq7%j(A1bfx~uaU)BNqv@`OvUwrqp$AW$kDkva!tD{1=1zG0wy~}^ z44Q~JiBcS0dm;BwWGqW~3#ZM5bH&cjbnrR6$54X;VGsn~==^Gd4eVmZ|1L%tQQD#5 zc%~Dk%?jZWgg#9>o(G*@XP=s=zuE6Fd)E|xc6oMuPftYr`TAMk-9o&@9b1q+Mv>#q zR@it$?R&Y806?l>wnW@P8yhiW-FYs3FaHjdW&qcd<{37 z620-ORvjf&?tTwvl|P|HyD#DTA9j5+znzq4mN@9Ve*!m6%B`H@F2%t-QaAAn%}}+p zYQe95>F4l*^$qY(z7Hpo;&E46{xxU8T~swE8$WFrnfA%o(ZIEc}gP{x&?w1UZv9Wt+`pnZjGG%DsPx*dV&OV3c^>( zv&hNG*AGZ>`l_nzv*>y6uB*sVyXU=O$Qw&|TIzyHhlBPzK`LWoxf3HT=`VVEkt)~( z>;wy@KlR;S_rwc|ND=bIDP_o1C%X3z%`p4N?!$xU8jk^L+|zEG*7N;5e)#LTC4C=w zKQxs%uFtno8^xiz(G=uwLdcV)DE_P!Op7(roCKFs&nYi3O<|v08bv6xJ-qS_30gj zmDh(BhIOOOV6&8latXWxL&Fo6WKR+J_aFsYvEr{ZEVUgkV8`c-VPiX@J&ecz*VEg5 z%+AoyVokQ^tsG6amtkQsT&S6p?H$<$dh0l^CyXnWyF$O_jjm#q^-eiuXlIl&tP1=3 z4N||0VkO!$Z&3U6rF#>HnGx~oT3@{f>EJko#x{?E=!Fm|2k*_ zbDFk>R$ndyA`QkW<4L?p&vy9oM>eBa^RzzrrUvm;Lg6&@b+N>@l2O)ar z0ulzq?r6LnSiJC1PazYZs^a35y}3)+Z@g@BXylFUMaIYYMFy)uam2GYxj;rx{3%7+XD>L( z%=;|AmvwdrQ0?uuYL}WPvBn`t#grXWhT?DT^sQUfj-Lc#1o&WB?OW&V$hCgQm0h|* zxZ*sliR=tZ|868YCPK=0rg+)hqn2lH}!HaUIu@>*o>Xqa@^>VNS_Kcf-^-OqU=;Qf}V+B>RT zxvjQTrOB3Z`*xfPB@#b+_zi+H1SK{ zy47>Twt}!tmaW+R&68s$m9BJ~Sr7JkU6wPeNS_mAcGsQYuajbFUnLW`t5(Z~y3DxF z&Ns0od&u$J+6OR4uQ>8&6 z0+Qyf7c7ot*^)`0b4Fxc8!;ay*R$UffLuhji&F9+ED8%MKG-@t3t6$7ZfAXe=nnv} zx08ZZ+aEuQNdH6HXJ=6@g9BK$$-Fbc7|60@-{F^D%;`@|d5L4fHClyhr)|#Du;Y3p z#7E3X8L6zStVRM(WHyN~yPpP0goqLnjTeU{#GXkF^z}2oLqp!&UR!hKB=LM7(h z5E$;rH+I@7X@Obfw%rv!eA}~z#J`ol=E{cx8?0Lz6Yg0U9??H@xaVh(G9Flgp*Zd( zoAz>aQn|i(YWGi&T7k?2B)|pvX@g`UCznfSW>fXPn&ox{EG#xfOVC%M!!A#28q&q% z)4%8*S=aGH&%RA^oZV;%hlM|H#DzUms=GgJsB&}9UbyB&BH>hk=obP+{>}s^Jgji% zi68x$T(^9RLJteW$|5{9#)UZG^SjivD5cq%g^6Bg*lRR27)=%-DsC@jGGt<+6oZNJ zRp=Eb&-QUPL=f{#Ou(y~qi94Ntj@X>0%;XLnT8N4z0*dv+XTJep!g~j6y!%_ApA)r z0#vykN`jVc!^V~C44F|se6q_SyiaovuZte9a{`%Yrlqr9kaHA1_wCUY7UR^EG&QB9dDXh|CpvstOTOPi~Q z*CkvW9FDNE(tsr$IN0v3C3#W%|2~bHS#@Bk=Xh}#O*PtFcFw~GmMw|weuKyRIWN~- zhvrF|n2{9;Tn(C&u8pagoBwTwxs%bW%^I68oA6|UvEny>2(Fgp z83s`}GE9gf-@`+#-No(gu+PPbW#X>sunv(4pgd;%`3PyV@$^?Kh>YiF_aTd)H!CdNYE)OwZ2 z^kHM;7*+l~zr(UqYlF8jFSo{w8!n6AS@$2gbon@6p(R!C+y(HOwxYvcnQWE#Dd#w@ z<|J7Oz3QFy9a$KCa0mgr$&}7{S8aK~d)=#6<+QbTf%|NCRa37>tKRFLmHEX;AX(|C zP#RJbzh<)9+!D!tc2vxb%>RopjPF)6_yQ78QsX5XEj4DYlJaisk(%k5@TppljUZI_ z;O7hlXSl}aRGT%6%Nws_w-)kGoVJ9b8TQ@Zc&6=W~4tUlUhl~ zg1s2vH=U(IK3A=KHot{tnG%~Wr9M-9urOH=lr+gJ+pjQr=4-UwhtkO;*@EdZhCT)e zV%3mlrmX(^I{l>+xjEfT#;3LVl$g|XbXJik=JN~Iiq_be=gnT*SaV*uYs8!fvWT-^ zQ4o$pLlzD|TO(Z|4}X@vdH9u`yX4koD}meZBbCl`3BFi085&XL^ARN6@TVefGh_mP8=SFvF{q?IuDT#-G1ULVo)GGpz_T$qtQG1FNnjvg#z-WfX zyh~lp56LWr2^c=)4ciiKT8rkOu^r4!Y@*OaO~V)o-8*rpLQ=DpAk>rrsw94}f{jx> zLyw^?C(oQ{QpAT!-PEasmXA2es?1jud}L%UA9a;_??B+_#_^}WuQKz-ENciT1(a$^ z2S>0NV#GY$NCscuTK$=ZhD$1^D=#vua|uG%lRA1t!F{!}R$DZ!?Xr921kvXU4mS}( z$j>Dr^OrrudI$PB-;^$uDpC%u50f<&!QPE4M6H}CM62 zP{eIA@-BB;ZMr& zA>IJM;*CLFV@KmM+k~yV@|$79|0CW2Opu^6rqJ%q|0#kY#+inK!RVzPqx6jQq=Yev z;k*0Wf}B{!nw2o{Ugd}#tO#`yhfL;+ZaziKS^_Jx;wf1Q3ZNE_mna3RbEEy|%L_GM zZtHc8AGbhc31{~7&3_#WZy3Yzr{Nl{7uQTIN}$2aWiT45QYD!Ttgfl?vHfZca&~v0 zV{|QUxSO8~BA%{yqJ@Gz7D|jR1F2y8(#U!e5`rEe-!l#8g+PN8kF5IoZvfk@!onRK z4fk6ZobIl~+|A4kTuB8rxW96#Q(ssa_6hDQoWYqlF>IjT`1Xwy!dL9-$h!NZn+8@J zy1g%Swcc;^uDYeAEl_t}4RMLNGuU1cQg!n;4#7t=Xu_nXWCVSI zOw5d|C&m94VecGW*ZYKzwvC;}wrw^!jqS#^Z8x@Ut5IVoO=H_?Y}<`-goAiXP%k)QT67tR_(p|3>_rMv2WqS1Z76~)c5wT<7;|H&${k?{C)*@ z#md59y?S$hRHD0jd7l05d7351{~VtDczPcdQ|ZCL%bVholB7K8{Ve@k-C*3YsnX-> zv2H)!EN5cDOG#02c=M+!-(3ziYe`~aa@LomKj-_`)4n+x&!;hsuc#;!hX|r^5rYk` zwx1@G5b^o%t|z)Q+YV))FG%H1mpqw3U4P1}eRIR6dC$ESwY6WDanCz$$Hi_qgVB*d zkAuKipxfK~clFEOP11owDqZh7n^#+0b1_9lSZ_G1jT+B|?APkfyYZ^EXXxmi4}1v! zK-t&w!rI~XRsTPKh@RH^&DeH~_79yjp87-!ro0;NLa3GcOA56L^OO}G!W+7Vj8?A} zTy%A9dA2f_SXF?T=sa0rs9Qej?*o-}rT~P1hg2du*xjR;GOI^;E&uW3P_?2=iDH_1 ze`g1H*8u$pz!M>%fJZM%TgK}Att~>|r_*(ES9Hu24|k7>mkF?&kw@DuTO4+a2eF)r zyxpv<$0;Gms@J=c;o%<>U1}>Jx|`ki4guD(8beWc4-YrD=CojAn3po;Dqu zW!Bqnbk|2e-m6EK2*|BGaw+sT-{&uIquQ*ION;`1Z9$j zH5-mO>@Smy^N=34dlQZ48OHtB9>GXy15#uL6bVChY{bHI^A={< zt#XcBnXU6;snQH7D@?6zZ7YdGsAfZxu|7BE=IW1*j4!sE*4JiE=vW87N8be1)w$Qw zMuusvo_TqB;XG~)>J%s;T9MauLl7ded5i_!gJzdQEX{AC2&|wGa4y!6x*`eK{ZnxX z$rZpzUc9_KZXM26IKT7X9b*jRni)}Bq(vR#;-m; zLLB`PoDJQ2J9a$#HtjsxayC>Wtyo-HPGz6Gr_`-D>MZAjg8AX4o-+&KQb+H&9V_K54ZQXRT7X%lFnv}CV+K0TLu~imb-n?5&YdEmEuU?ak1|6`ozO~ zeSgM>g^n&T+7R=4!uB5*pwK@p1muw3-oQfSdUL`f4+t?Z;&tVlw>S7=D!k;26xnZ( zVxS99UyNS~WB9IOZA~|9!GFCB8L!u1lNTA=pj;kI)i{CLo$p406c3OEYa@?1hle@a z-`h)0_qd$rW_^A2lFj5&mRA$LfBEs_XtT!<7A_(LDPm}hHg`$M#l>cIF%HbUN5GO*Qu?PD=u)Cf1MXpGT)QZ ztF?DJHDgNWTmLh{8WQLUEo(cjfO48ahp+*&azu`B!{fOpI2$(Tsy9q2BnD4TMyiRv z-SNJ2D_-$ft#KE(|LI_x9WcK(ii$LcI!^8fmNy%~5_}8MWlvW*$FG_)1N9ty8wf9E+be}!dr;|`A{p)& zn$l!&eO>M=tBCU9JMGqW=D4=_ddL3PlZ!kkM1sm6RXR+zck5swd+R*M=asz0av8z_ zm5n8Twzr)(I$S~5t0Phv8xlF$p`h1WP7ttq`CWP^IoF@`7AJ{WO{DpJwsy9GRL?`f z`=)&hrCj#oV+z9Co6i@5`x0HB_w5*Z#;%(a&vRcIWUk}eVEvxpFL#5!fIqJEc@h!C zvg~^7?dd=|RM4p8I5lW}WabMfcE0C(uoD)3kNuPD{fwx7Hr?0uT*X^q`Sb@&3I%06 zDhBtuSBa4cxEwpi<7c(|0=`vm_x1%-*7u-oDxt7(o_ zgTuRD4;w-zpJ#ua97vW2M*D4fd6`qk`*LQYlgH^{F$PrWdw;5`TIrLWIyjo9WRu~0 zoZR)ESKUV%^Tp|4vL-z}J*a5{GeW!TX%7lAz<%KLTF2|rxpf|_fCSLZd-$beG#e)cX5Jdd{#Vxh6;O6qvO zPkZveZ{Zfzn)Lae+3q_5bdv7;`{kG_VGftQ22GOp)uXCY_f~)Oa=m-`sZX=<2q+{P zZw41$>a4TQn8$<-C*zhmO>?PsrmJMK$b6dwF+{J!Xt|q_vIZzhJXmOVT25wj-bA!& zp?fP{iq}5iakJFKjOQ9pE{rfVU1+qD5bMgUl+v}KsM-s_mExTLW#B0qc!YxbjrgIZ_&8dP(+nvm*LTc4wCViYJud_EJi+(nk` zmY7~XgPl-lB>s$ZekL0)t3pp9DIzNgyV2e$uxHpxxr~_c`>;@45IX}&Z@`9wyRg?l z*(VC<3>F=?gGVt{{+mflsKMqk{ZD`_3o!r^>0x&Pv^SR4;=B^A=lI+u zZ**cuNx8l$G{D2wje1FGI-aU1FF&~X)0{+JQKR@96#yF$cr zFm=^rW^(wwSIvGmxNhX7=y>(Fw(t-JbQ`eL^JFQSzvTf%42+%6vU&1)-t;uXMK(6^ z%mtz-Bjrr`FN+r!lK_BOX|RbpL?l9SSR8K|}vf^;@>dMfIhF6J#)(e|XK^}Jkj#p7~oUh)y|I*W|J#pg59b9WEQ zqON}#Fq%|L)_0Ycd#Ur%HstypUw;?FkTqrI5J+zBNKD^^d2ra*{9AB-A7bGlre~Lh ze(ct{50oGhq7puJ`YxU93qSB?TI&O{{5r6Gsd2VAAK)cNuPD}Ngh?=T zOz^=5Z7pTuCm(4KH}?D4f=n9w;=Q;_bozzR9%I`W>9IJh=hn2{P;1n*eWTpFii|E7 zoAIgWZQ^c(;5P_|@lt9O**jZ8E8~eyTMQ7d*x3m#k7MCRdQ9V>kjVz8YN9)T#+vKX zLm4{65_M6&=GmcC=L@|EeRyV_Te_M`{vHwm^UZ2-&RquFO7z>3eM~1xTqyW+>uz2D z8ux^YI|W~-aP58pqnwL7{!f=FNynn}=F&-I2$;(~bizcPM}L6e8lG4fXlNS2huimk zT3VG!37Bdne?j4xn|F1q)_;KyK_U5;x~O_ zCz1dtG5jxH^(qBL=Fo@cXJ_6NGNI2W*R}-eEs<5`M#9Z?tRNDS$Y6ZpyjK89v|Zdi z&a+2>+)YMH)l$~;0X%;8;40^)W>zOL6=*i5y5aqBnXYLfb9;5=b$aXS-QGbV9a=Es zWN)9wVk{_qLxdP|y-14m+Hz={{uVnfl(ITBbhxX!4i9t4X|tO8pk(T^@{^h+AEXU; ztQ}iX9TXF@uvkMnp(}nyNU&V3s|yr=G-qDg8&+Ccdpm0Y)vuFFQ-?3N_jkdsd2)nd zfWRkCT)b&9^s~^Aa$dsih22Km`($%X0VF(hiMfKXurJPUpO;IVj&3IRs|Od^kO^0f zyFVSa?YH_}xt%qn!DH#%&adaV@7I&Bnr}}7E}%kP?1N(D8qK@4PLEQ2YvL|Q+4Fe^KV&sCeN}yMr8wMa!s}S z^tfp?XUC{4Q@L#^F~d(b)<4eIMuJT$p5Fp_cB0XhXnh_%x<(zBYc+@FtM3mDQs`q( zG))OQ&UbdNGn;(fzdDK4O`!_csg5hYzTGl73nxI>aRJ0t`Nm_{l1_b{*$S_m&(9{T z&nl+B{sUZRM`wbm85tWTF}MgG>9+&g&?`ezXM$=vIs(2;C3%{4ZyrNnjL$&W65JH(v~Bybe;k zR_go_P<(%Kahxgftk{-#;AxQrio`5}H1_qg(q&E03Y5y75m+bHd2gSBO&M;g>q^L& z_SWYDZj!giDQ_#IY~~Hz3w!@u(M(EYiB`vqO)%;9=g+axW(}#2wo3nIr>tlZsZY=bC zN3Gnp^o7qpINDSmZSYYjpjQmmD1OjPS3%4+m28568oj9E`7kh2+@JKzF1ZDXje>Y} zEu^|v4A4lcU0y~QuR-gSYOcmjizEp>Eh9YzU7|tA9?BJ9kE*_@t5?oh93pM^kr#b@ z>vU*|?=vwrHkKl*YG}ae5Fo^!>Pm6k|K`%H7ASzB8Z^bcrWC8+w4^tH)ISZ#!U zsjaEBqJo5Jl5z6m)iEo~egTx&Dags{W>6mxvXTB4s@GWa6B;;xq@$z3M*b|yN%8l@ zg)vR4#>#WMCtq4e#iFzE*qHp!G7C!@8p#I+q9i3d2lsx4z~lTnn@SJ3(FWv))-j;Y zP95%+$24_r169mcQ&EHiMzlMxgl77toh8Sin=wIGk=FVf=Nv8{wnGK_n4NigcVg-> z8IoeC{GUI4yh@^#2hN8 zHTU@cWe+XSv-dpAUCORIkmxCW&&aniIT*h4??lfb&O#stAc$UJU> z|M=U!--(zYMye|&tdHRkJEhk^^(ia4UpX)D_3jL`wJx^RyB()ejdsRwCSnB7&QWX6s=HTscakpa>=oP6n){Fa)fE z-{!7qC$0BdiK0(iT+%u4L&U)L?jGjsZ0V05yG2FlU*ZjIWyco)IU~ zr_$(;wX;k#r}5NP1`~&I3Z}5|R#i)8K0{3|Cl~TkA_OZ=vwX8?C>7f#eySSES5F$d z3}L7=mZAajT6vzulG?}6A64q;508!%=~WMIpPq^;iTNsb<5pX(*eFq8J*F^u7AI3~ z>P^J1F5ZaUAA_8v0JjDK`G5l-?QpF5onV3QWAc~NCf8}497#E!_a_O*fhtDbHDjfU zr5Qp{yIvKBa_jTR0$ibo&j$C%x!$o;_%|tE$*h`3L#Ywr+9&=rU$1oYL#nm+T})2{C>jVx^1EIG|J7xxJcad8p_6}oAO@i0Jlr%iiWldCOf2^1NmKV4lj<1v zr)>p#KeDaFv*8-ls%sIX_>A1zohwn71Kl&nUkIv3L{m-|FK6hRj5a=i5~fPO{RjoK zb9ChMAt81Vc9N9zkdM`M54yS9&0N-qVlQ)wRWVJs9y(e5(jcj=t}X&)&mBFDqX4W| z4TZD}Ao2W%7WsyRFMML3llN`bcF4lg&|iAMEOW(0>gmC)u-F8cUYlM$NUX zX(l>{>vZ?f{=w{JaIw7o*>vBck-VdQ)OxXYi7uUVwiK@jB67ZwEULuv8%5Pb84K(y)+19yY<1UiA-jcl=L@EA!Q7Sj z`cLv129xO=5>a?UK@W{O(iYShPVZ&#R&sCejswjMM3N7BQ`WA;;CAxOaKuiZSA!!lp=t3Dgk3He?r!)u5ks z0I8An$(5K4IEfYr?}Xu>1l>;QaMu=ALl8{GgwfYz*t4*4^uQDD_~kv#ttxUh2CJ_v zD&ftz&|8p;q~(1f%N&Nj|EPQ0h1?Na$cd#YD)!ltQW!^)?+)W*AS)D zwo)vg!h+Fv9xI{+ERszTLwn$)8IwzY)W;|aQsWMS;W%~1V21-KHxDsf1PY9`_*YfU zXRKlZYdq;^Rb#9*(Tq$l@d<0|grS`W#wKH8&$p@+>$>)LJRjmH(Whi)=N@`t8P$!d z&PNo%gh$ZUuSm!c&|B&#oVniWte68NO$9L92VkOqA^2yrrf;=yBN*n&7+d&+)8z{Ms^LeS`AfkKZ#y@4?mb51wW581YhRGQQLxvyIn$(c{G24@pcsdR~d_IdmJQk>!Czm)rO2szrVjp5H-j5-Pl zLb=MuO3gY)fT$kkIGZ(`Jro8Dc}~feGrh~fGijA)fMwHeC%VsM>c5`uR%$tUYobs4 zRs9(p>B5yG5fjZEEEo~)j1GV6~&lvQ_sIanl4o^pg8o=dis_#N6t(BkVKQd8VcM5{~qW&aIAf4rAlZ}EZn zrM=L~yK5zR16@5sd)l+NLO-*@$oqn4Jsv+2?3Wv1-V@%xZo5Z$UTyz{&;{f zUvS1MS51LWSy|!Y{23tRnQ+vli*itCU9bq=RxN`iXKIAq}(Cv z7_#OGA0>n$sA(ci8xI0);3NmpkG*6@iT|N>NvSCTP|{ti2$U+3Bm6H6{2ZOI}~0tRzGe*VyHhzjVsX+q)fb z9#01|)$UqneK(^U?+?cSL=sT=>}#|9^qk`>(OYHN^~}xKMMOnKbgbA2L;wRa?fSrw zsF%lk{HC>5&j~-6?S5bneXkM#O;(@zu%CCl3-s)>7o0JK-A>S1wPmyr+rEKmS2mRk zYbbd1byQ{GWKT=gu{s9;^Gwn6UZx)sM^gv=I#J9^g|lkpswnqxBc4Surd5}x-RL;Qmx|?1=-!Fc9z_;

-Gp<)}-6^z^< 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 XDvSDh6buV?4w)KR+{XUx{_?*8p#q}o literal 0 HcmV?d00001 diff --git a/artifacts/resourcediagram/05-rapportageperiode-14-sep-tot-11-okt.png b/artifacts/resourcediagram/05-rapportageperiode-14-sep-tot-11-okt.png new file mode 100644 index 0000000000000000000000000000000000000000..aab8ffe263c4d096bf97a29e0aff0a64f45f6185 GIT binary patch literal 123897 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

+3|;)bk^m3Sp^n^|gH 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�|FYq`7jo7^47VrhXWEZH*4ng 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 Q@b`Iu5(?ra?+pF_7yReLj{pDw literal 0 HcmV?d00001 From 9ecbe4bf8a7117e38db82390f467dd5c16d4bae2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 16:03:52 +0000 Subject: [PATCH 5/7] =?UTF-8?q?Rapport:=20resourcediagram=20=E2=80=94=20re?= =?UTF-8?q?viewfixes=20curvetoestand,=20ruitklem,=20lege=20staat,=20kolomb?= =?UTF-8?q?reedte=20(#113)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hyperkritische review op de drie punten van manuvarkey ("nee, nog niet"): 1. De Curve-kolom zei "Uniform" waar de app zelf "Contour" of "Geïmporteerde curve" zegt: de engine las alleen `assignment.curve`, terwijl de lastverdeling (`assignmentDayUnits`) en het eigenschappenpaneel drie lagen kennen. `RowAssignment.curve` is nu `RowCurve` (contour op de taak via `matchContoursToAssignments` > `curveValues` zonder OPS-vorm = 'imported' > `curve`, afwezig = UNIFORM); het paneel geeft de twee toestandslabels uit `task:properties.assignments.*` mee. 2. Met een tijdvenster kon een mijlpaalruit (en de baselineruit) op de eerste vensterdag bij lage zoom half over de tabel hangen: de zichtbaarheidstest was geen klem. Het middelpunt wordt nu binnen [tabelrand + size, chartrand − size] geklemd; de testrecorder legt gevulde paden vast en bewaakt dat niets in het chartgebied links van de tabelrand komt. 3. `emptyPeriod` wees naar de periode terwijl er wél taken in het venster stonden zonder toewijzing: nieuw `counts.inPeriod`, de melding komt alleen als er géén bladtaak meer in het venster is. 4. De default (kolommen aan) blijft — het resourcediagram is nog in geen release uitgeleverd, dus er is geen bestaand gedrag dat breekt — maar de gidsen nl+en zeggen nu expliciet dat de tabel breder en de tijdas smaller wordt, en wat *Contour*/*Geïmporteerde curve* betekent. 5. Gecomprimeerde as + venster zonder werkdag kleefde naar een datum buiten het venster: valt voor dat venster terug op de kalender-as. 6. Curve-kolom 75 → 92 px (pl/pt-labels pasten niet, gemeten op Inter). 7. De 3 px-minimumbreedte van een balk steekt niet meer over de chartrand. 8. Kleurvakken (resourcekleurmodus) worden op de ruwe tijdas verdeeld en per vak op het chartgebied geknipt, zodat een afgekapte balk de kleuren van het zichtbare stuk toont. 9. Relaties worden bij een venster nooit getekend (`showDeps && !windowed`) i.p.v. alleen per conventie. 10. Lege-staat-tekst gaat door `fitText`; gids: het getal achter een typeband telt taakrijen. Regressie: check-reports (imported/contoured/OPS-vorm/contour-per-resource, inPeriod), check-print-report (ruit binnen het chartgebied bij 4 px/dag, minimumbreedte geklemd bij 1 px/dag, tabelbreedte +137). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq --- public/docs/en/gids-rapporten-printen.md | 10 ++- public/docs/nl/gids-rapporten-printen.md | 10 ++- src/components/panels/ReportPanel.tsx | 8 ++- src/engine/reports/resourceGantt.ts | 48 ++++++++++--- src/services/print/printPreview.ts | 88 ++++++++++++++++-------- tests/planning/check-print-report.ts | 27 +++++++- tests/planning/check-reports.ts | 35 ++++++++-- 7 files changed, 171 insertions(+), 55 deletions(-) diff --git a/public/docs/en/gids-rapporten-printen.md b/public/docs/en/gids-rapporten-printen.md index 1f62bf69..a1a47c3b 100644 --- a/public/docs/en/gids-rapporten-printen.md +++ b/public/docs/en/gids-rapporten-printen.md @@ -104,8 +104,9 @@ left out — an assignment on a summary task (which an import can produce) is no at the bottom, so the meeting can see at a glance what nobody owns yet. **Group by resource type** adds a layer above: first a band per type — labour, crew, subcontractor, equipment, material, in that fixed order — with the resources inside it; useful when a meeting discusses the people first -and the equipment after, or when you only want to print the equipment block. With *Each resource on -a new page* a type band starts a new sheet together with its first resource. **Reporting period** — +and the equipment after, or when you only want to print the equipment block; the number after a type +band counts the task rows below it, so a task under two resources of the same type counts twice. +With *Each resource on a new page* a type band starts a new sheet together with its first resource. **Reporting period** — the same control as on the table reports, see further down — limits the diagram to a time window: only tasks that touch the window take part, the time axis runs exactly from the start to the end date, and a bar that continues beyond it is cut off at the edge. That lets a meeting put the @@ -116,7 +117,10 @@ default: how many units per day the band's resource is assigned to the task, and distribution curve — "you are on Foundations half days, front loaded" says more than "you are on Foundations". A task under two bands shows each band's own load; two assignments of the same resource on one task are added up, and a dash in the curve column means those assignments carry -different curves. Turn **Show units/day and curve** off for a narrower table. +different curves. If the assignment has its own hour distribution or an exact curve imported from +MS Project or P6, the column says *Contour* or *Imported curve* respectively — the same as in the +properties panel. The two columns make the table wider and the time axis narrower; turn **Show +units/day and curve** off for the narrow table of the Gantt print. The settings of the Gantt print apply here too — critical path, float, bar colours, status line, paper, header repeat — with three exceptions: *Follow view* (this report doesn't take its rows from diff --git a/public/docs/nl/gids-rapporten-printen.md b/public/docs/nl/gids-rapporten-printen.md index 3fb74d21..2994684f 100644 --- a/public/docs/nl/gids-rapporten-printen.md +++ b/public/docs/nl/gids-rapporten-printen.md @@ -107,8 +107,9 @@ resource meenemen** komt er onderaan een band *(geen)* bij, zodat je in het over nog niemand heeft. Met **Groeperen op resourcetype** komt er een laag boven: eerst een band per type — arbeid, ploeg, onderaannemer, materieel, materiaal, in die vaste volgorde — en daarbinnen de resources; handig als je in het overleg eerst de mensen en dan het materieel bespreekt, of alleen het -materieelblok wilt afdrukken. Bij *Elke resource op een nieuwe pagina* begint een typeband op een -nieuw vel samen met zijn eerste resource. Met **Rapportageperiode** — dezelfde keuze als bij de +materieelblok wilt afdrukken; het getal achter een typeband telt de taakrijen eronder, dus een taak +die onder twee resources van hetzelfde type staat telt twee keer. Bij *Elke resource op een nieuwe +pagina* begint een typeband op een nieuw vel samen met zijn eerste resource. Met **Rapportageperiode** — dezelfde keuze als bij de tabelrapporten, zie verderop — beperk je het diagram tot een tijdvenster: alleen taken die het venster raken doen mee, de tijdas loopt precies van de begin- tot de einddatum, en een balk die verder doorloopt wordt aan de rand afgekapt. Zo leg je in dezelfde vergadering het look-ahead-rapport @@ -119,7 +120,10 @@ dag de resource van die band op de taak staat en met welke verdeelcurve — "je op Fundering, vooraan belast" zegt meer dan "je staat op Fundering". Een taak onder twee banden toont per band de eigen inzet; twee toewijzingen van dezelfde resource op één taak worden opgeteld, en een streepje bij de curve betekent dat die toewijzingen verschillende curves hebben. -Zet **Eenheden/dag en curve tonen** uit voor een smallere tabel. +Heeft de toewijzing een eigen urenverdeling of een uit MS Project of P6 geïmporteerde exacte curve, +dan staat er *Contour* respectievelijk *Geïmporteerde curve* — hetzelfde als in het +eigenschappenpaneel. De twee kolommen maken de tabel breder en de tijdas dus smaller; zet +**Eenheden/dag en curve tonen** uit voor de smalle tabel van de Gantt-afdruk. De instellingen van de Gantt-afdruk gelden hier ook — kritiek pad, speling, balkkleuren, statuslijn, papier, kopherhaling — op drie na: *Volg weergave* (de rijen komen bij dit rapport niet van het diff --git a/src/components/panels/ReportPanel.tsx b/src/components/panels/ReportPanel.tsx index 87f23542..39a0cbc0 100644 --- a/src/components/panels/ReportPanel.tsx +++ b/src/components/panels/ReportPanel.tsx @@ -460,7 +460,9 @@ export function ReportPanel() { BACK_LOADED: tCommon('resource.curve.backLoaded'), BELL: tCommon('resource.curve.bell'), EARLY_PEAK: tCommon('resource.curve.earlyPeak'), LATE_PEAK: tCommon('resource.curve.latePeak'), DOUBLE_PEAK: tCommon('resource.curve.doublePeak'), TURTLE: tCommon('resource.curve.turtle'), - }), [tCommon]); + // Dezelfde twee toestanden als het eigenschappenpaneel: contour op de taak, geïmporteerde curve. + contoured: tTask('properties.assignments.contoured'), imported: tTask('properties.assignments.importedCurve'), + }), [tCommon, tTask]); // Rapportageperiode als tijdvenster (punt 3): dezelfde oplossing als het control toont; bij // *Hele project* geen venster, zodat het rapport byte-identiek blijft aan vóór deze optie. const resourceGanttPeriod = useResolvedPeriod(resourceGanttOptions.period); @@ -559,8 +561,10 @@ export function ReportPanel() { labels: { // Resourcediagram zonder één toewijzing: zeg wat er ontbreekt, niet "geen taken" — tenzij er // écht geen taken zijn, dan is "wijs resources toe" het verkeerde advies. + // Leeg door het venster (géén bladtaak meer in de periode) ⇒ wijs naar de periode; leeg terwijl + // er wél taken in de periode staan ⇒ die zijn niet toegewezen, en een andere periode helpt niet. noTasks: reportType === 'resourceGantt' && tasks.length > 0 - ? (resourceGantt && resourceGantt.rows.length === 0 && resourceGantt.counts.outsidePeriod > 0 + ? (resourceGantt && resourceGantt.counts.inPeriod === 0 && resourceGantt.counts.outsidePeriod > 0 ? t('resourceGantt.emptyPeriod') : t('resourceGantt.empty')) : t('noTasks'), diff --git a/src/engine/reports/resourceGantt.ts b/src/engine/reports/resourceGantt.ts index e9c4e977..ab2a9eb7 100644 --- a/src/engine/reports/resourceGantt.ts +++ b/src/engine/reports/resourceGantt.ts @@ -1,6 +1,7 @@ import type { Task } from '@/types/task'; import type { Resource, ResourceAssignment, ResourceCurve, ResourceType } from '@/types/resource'; import { encodeBandKey, encodeGroupedTaskRowKey, NONE_RAWKEY, type ViewRow } from '@/engine/view/visibleRows'; +import { matchContoursToAssignments } from '@/engine/contour/contourEngine'; import { dayOf, taskFinish, taskStart } from './reportCommon'; import type { ResolvedPeriod } from './reportingPeriod'; @@ -45,7 +46,12 @@ import type { ResolvedPeriod } from './reportingPeriod'; * * Per taakrij onder een resourceband levert `assignmentByRowKey` de TOEWIJZING van die band op * die taak (punt 1): eenheden per dag en de verdeelcurve — de rij is een taak, maar wat de lezer - * wil weten is "hoe zwaar staat déze resource erop". Twee records van dezelfde resource op één + * wil weten is "hoe zwaar staat déze resource erop". De curve volgt dezelfde drie lagen als de + * lastverdeling zelf (`ResourceLoad.ts`'s `assignmentDayUnits`) en het eigenschappenpaneel + * (`TaskAssignmentsSection`): een opgeslagen CONTOUR op de taak wint (`'contoured'`), dan een + * exacte geïmporteerde curve zonder OPS-vorm (`curveValues` zonder `curve` ⇒ `'imported'`), dan + * pas `curve` (afwezig = UNIFORM) — een P6-/MSP-import met een front-loaded curve mag hier nooit + * "Uniform" heten (hyperkritische review, bevinding 1). Twee records van dezelfde resource op één * taak (één rij) worden opgeteld; de curve is alleen bekend als alle records dezelfde hebben * (anders `null`, de render toont een streepje). De "(geen)"-band heeft geen toewijzing en dus * geen entry. De render tekent dit als twee tabelkolommen (`PrintOptions.assignmentColumns`). @@ -91,15 +97,20 @@ export interface ResourceGanttRowsResult { unassignedTasks: number; /** Bladtaken die door het tijdvenster zijn weggelaten (0 zonder venster). */ outsidePeriod: number; + /** Bladtaken ín het venster (zonder venster: alle bladtaken) — ook als er geen toewijzing op zit. */ + inPeriod: number; }; } +/** De curvetoestand van een toewijzing: contour op de taak, geïmporteerde exacte curve, of de OPS-vorm. */ +export type RowCurve = ResourceCurve | 'contoured' | 'imported'; + /** De toewijzing achter één taakrij van een resourceband (zie de moduledoc). */ export interface RowAssignment { /** Som van `unitsPerDay` over de records van deze resource op deze taak. */ unitsPerDay: number; - /** De curve als alle records dezelfde hebben (`undefined` in een record = UNIFORM), anders null. */ - curve: ResourceCurve | null; + /** De curvetoestand als alle records dezelfde hebben (`curve` afwezig = UNIFORM), anders null. */ + curve: RowCurve | null; } /** Bandsleutel van de "(geen)"-band — dezelfde codering als de schermgroepering. */ @@ -181,9 +192,27 @@ export function computeResourceGanttRows( // een toewijzing aan een onbekende resource telt niet (zelfde regel als `resourceNames` op het // scherm: die taak is dan "zonder resource"). const tasksByResource = new Map>(); - // Per resource-id × taak-id de opgetelde eenheden en de verzameling curves (punt 1). - const loadByResourceTask = new Map }>(); + // Per resource-id × taak-id de opgetelde eenheden en de verzameling curvetoestanden (punt 1). + const loadByResourceTask = new Map }>(); const assignedTaskIds = new Set(); + // Geldige records per taak, voor de contourkoppeling (dezelfde `matchContoursToAssignments` als + // de lastverdeling en het eigenschappenpaneel: per taak, in recordvolgorde). + const validByTask = new Map(); + for (const a of ctx.assignments) { + if (!leafById.has(a.taskId) || !resourceIds.has(a.resourceId)) continue; + const list = validByTask.get(a.taskId) ?? []; + list.push(a); + validByTask.set(a.taskId, list); + } + const contouredIds = new Set(); + for (const [taskId, list] of validByTask) { + const task = leafById.get(taskId)!; + if (task.timephasedContours && task.timephasedContours.length > 0) { + for (const id of matchContoursToAssignments(task.timephasedContours, list).keys()) contouredIds.add(id); + } + } + const curveOf = (a: ResourceAssignment): RowCurve => + contouredIds.has(a.id) ? 'contoured' : (!a.curve && a.curveValues ? 'imported' : (a.curve ?? 'UNIFORM')); let assignments = 0; for (const a of ctx.assignments) { const task = leafById.get(a.taskId); @@ -194,9 +223,9 @@ export function computeResourceGanttRows( bucket.set(task.id, task); assignedTaskIds.add(task.id); const loadKey = `${a.resourceId}\u0000${task.id}`; - const load = loadByResourceTask.get(loadKey) ?? { unitsPerDay: 0, curves: new Set() }; + const load = loadByResourceTask.get(loadKey) ?? { unitsPerDay: 0, curves: new Set() }; load.unitsPerDay += a.unitsPerDay; - load.curves.add(a.curve ?? 'UNIFORM'); + load.curves.add(curveOf(a)); loadByResourceTask.set(loadKey, load); } @@ -266,6 +295,9 @@ export function computeResourceGanttRows( return { rows, assignmentByRowKey, - counts: { resources: bands.length, assignments, unassignedTasks: unassigned.length, outsidePeriod: allLeaves.length - leaves.length }, + counts: { + resources: bands.length, assignments, unassignedTasks: unassigned.length, + outsidePeriod: allLeaves.length - leaves.length, inPeriod: leaves.length, + }, }; } diff --git a/src/services/print/printPreview.ts b/src/services/print/printPreview.ts index a2a1ff1a..a745597a 100644 --- a/src/services/print/printPreview.ts +++ b/src/services/print/printPreview.ts @@ -26,11 +26,11 @@ import { visibleBarColorCategories, type BarColorContext, } from '@/services/print/barColorCategories'; -import type { Resource, ResourceAssignment, ResourceCurve } from '@/types/resource'; +import type { Resource, ResourceAssignment } from '@/types/resource'; import type { ActivityCodeType, CustomFieldDef } from '@/types/structure'; import type { BarColorSelection } from '@/types/barColor'; import type { ViewRow } from '@/engine/view/visibleRows'; -import type { RowAssignment } from '@/engine/reports/resourceGantt'; +import type { RowAssignment, RowCurve } from '@/engine/reports/resourceGantt'; import type { BaselineOverlay } from '@/types/baseline'; // BASISmaten bij rapport-lettergrootte 100%. Niets tekent hier nog rechtstreeks mee: alle @@ -93,7 +93,7 @@ const COL = { // Toewijzingskolommen van het resourcediagram (manuvarkey punt 1): eenheden per dag en de // verdeelcurve van de resource van de band op die taak. Alleen bij `assignmentColumns`. units: { w: 45 }, - curve: { w: 75 }, + curve: { w: 92 }, }; /** @@ -389,7 +389,9 @@ export interface PrintOptions { * OPTIONEEL — rapportageperiode als TIJDVENSTER (manuvarkey op #113, punt 3; resourcediagram): de * tijdas loopt exact van `from` t/m `to` (ISO-dagen, inclusief) zonder de gebruikelijke marge van * 7/14 dagen, en balken, mijlpalen, speling, voortgang en baseline worden op de chartrand - * afgekapt — `Draw2D` kent geen clip, dus de geometrie zelf wordt geklemd. Welke rijen in het + * afgekapt — `Draw2D` kent geen clip, dus de geometrie zelf wordt geklemd (ook de 3 px- + * minimumbreedte van een balk en het middelpunt van een ruit; relatiepijlen worden bij een + * venster helemaal niet getekend). Welke rijen in het * venster horen beslist de rijenbron (`computeResourceGanttRows`), niet de render; een rij die * er toch buiten valt tekent gewoon geen balk. Relatiepijlen worden niet geklemd: het venster * wordt alleen aangeboden op het resourcediagram, dat er geen tekent. Afwezig ⇒ byte-identiek. @@ -405,8 +407,9 @@ export interface PrintOptions { */ assignmentColumns?: boolean; rowAssignments?: ReadonlyMap; - /** Vertaalde curvenamen (`common:resource.curve.*`); een ontbrekend label valt terug op de enum. */ - curveLabels?: Partial>; + /** Vertaalde curvenamen (`common:resource.curve.*`, plus `contoured`/`imported` uit + * `task:properties.assignments.*`); een ontbrekend label valt terug op de enum-/toestandsnaam. */ + curveLabels?: Partial>; /** * Lettergrootte van het GEGENEREERDE RAPPORT als percentage (issue #25 punt 4). 100 (of * ontbrekend) = het oude gedrag, byte-identiek. Werkt bewust RELATIEF: tekst, rijhoogtes, @@ -692,7 +695,7 @@ export function renderReport( d2d.fillStyle = PRINT_COLORS.textSecondary; d2d.font = m.font(14); d2d.textAlign = 'center'; - d2d.fillText(options.labels?.noTasks ?? 'No tasks to display', 300, 100); + d2d.fillText(fitText(d2d, options.labels?.noTasks ?? 'No tasks to display', 580), 300, 100); // Geen kop-/tijdschaalstrook in de lege-staat (alleen een centrale melding) ⇒ niets te herhalen. // Het meldingsvak zelf houdt z'n vaste 600×200; alleen de tekst erin volgt de schaal. return { width: 600, height: 200, tableWidth: m.tableWidth, headerHeight: 0, footerHeight: 0 }; @@ -730,11 +733,20 @@ export function renderReport( // scherm-Gantt beslist of de kalender werkelijk gecomprimeerd kan worden (een kalender zonder // werkdag valt gecontroleerd terug op de gewone kalender-as). const calEngine = new CalendarEngine(calendar); - const compressed = isCompressedEffective(calEngine, !!options.compressNonWorkdays); - const measureAxis = resolveGanttAxis({ + let compressed = isCompressedEffective(calEngine, !!options.compressNonWorkdays); + let measureAxis = resolveGanttAxis({ calendar: calEngine, compressNonWorkdays: compressed, origin: minDate, chartOriginX: 0, zoom: 1, scrollX: 0, }); + // Tijdvenster zonder één werkdag (b.v. een weekend) op de gecomprimeerde as: de as zou dan naar + // de eerstvolgende werkdag búiten het venster kleven. Val voor dít venster terug op de kalender-as. + if (options.timeWindow && compressed && measureAxis.daySpan(minDate, maxDate) < 1) { + compressed = false; + measureAxis = resolveGanttAxis({ + calendar: calEngine, compressNonWorkdays: false, + origin: minDate, chartOriginX: 0, zoom: 1, scrollX: 0, + }); + } const timelineDays = compressed ? Math.max(1, Math.ceil(measureAxis.daySpan(minDate, maxDate))) : calendarDays; @@ -1038,8 +1050,11 @@ export function renderReport( const x = dateToX(date) + zoom / 2; const cy = y + barHeight / 2; const size = barHeight * 0.45; - // Tijdvenster: een ruit buiten het chartgebied wordt niet getekend (geen halve ruit). + // Tijdvenster: een ruit die het chartgebied helemaal mist wordt niet getekend; een ruit op de + // rand wordt met zijn middelpunt naar binnen geklemd, zodat hij nooit half over de tabel of + // over de rechterrand hangt (hyperkritische review, bevinding 2). const inWindow = !windowed || (x + size >= m.tableWidth && x - size <= canvasWidth); + const cx = windowed ? Math.min(canvasWidth - size, Math.max(m.tableWidth + size, x)) : x; if (inWindow) { const advies = colorAdvice(task); @@ -1050,17 +1065,17 @@ export function renderReport( d2d.lineWidth = 1; } d2d.beginPath(); - d2d.moveTo(x, cy - size); - d2d.lineTo(x + size, cy); - d2d.lineTo(x, cy + size); - d2d.lineTo(x - size, cy); + d2d.moveTo(cx, cy - size); + d2d.lineTo(cx + size, cy); + d2d.lineTo(cx, cy + size); + d2d.lineTo(cx - size, cy); d2d.closePath(); d2d.fill(); if (advies.outline) d2d.stroke(); // Task name label (rechts van de ruit, valt terug naar links/ellipsis bij de rand) if (options.showTaskNames) { - barLabelJobs.push({ name: task.name, barRightX: x + size, barLeftX: x - size, y: cy + m.s(3), bold: false }); + barLabelJobs.push({ name: task.name, barRightX: cx + size, barLeftX: cx - size, y: cy + m.s(3), bold: false }); } } } else if (task.childIds.length > 0) { @@ -1126,10 +1141,11 @@ export function renderReport( const segments = task.splitGaps && task.splitGaps.length > 0 ? computeSplitSegments(task.splitGaps, start, end, false, calEngine) : [{ start, end }]; - const segs = segments.map((s, i) => ({ - x1: clampX(i === 0 ? rawX1 : dateToX(s.start)), - x2: clampX(i === segments.length - 1 ? rawX2 : dateToX(s.end)), - })).filter(s => !windowed || s.x2 > s.x1); + const segs = segments.map((s, i) => { + const rx1 = i === 0 ? rawX1 : dateToX(s.start); + const rx2 = i === segments.length - 1 ? rawX2 : dateToX(s.end); + return { rx1, rx2, x1: clampX(rx1), x2: clampX(rx2) }; + }).filter(s => !windowed || s.x2 > s.x1); const split = segs.length > 1; if (split) { @@ -1142,15 +1158,24 @@ export function renderReport( } for (const s of segs) { - const sw = Math.max(s.x2 - s.x1, split ? 2 : 3); + // De minimumbreedte (3 px, 2 bij splits) op de RUWE maat, en het einde bij een venster op de + // chartrand geklemd zodat dat minimum er niet overheen steekt. + const rawSw = Math.max(s.rx2 - s.rx1, split ? 2 : 3); + const sw = (windowed ? Math.min(s.rx1 + rawSw, canvasWidth) : s.rx1 + rawSw) - s.x1; if (advies.kind === 'segments') { - let sx = s.x1; + // Kleurvakken op de ruwe tijdas verdeeld en daarna per vak op het chartgebied geknipt: een + // afgekapte balk toont zo de kleuren die bij het zichtbare stuk horen (review, bevinding 8). + let sx = s.rx1; advies.segments.forEach((seg, si) => { const isLast = si === advies.segments.length - 1; - const w = isLast ? s.x1 + sw - sx : Math.round(sw * seg.weight); - d2d.fillStyle = seg.color; - d2d.roundRect(sx, y, w, barHeight, si === 0 ? 3 : 0); - d2d.fill(); + const w = isLast ? s.rx1 + rawSw - sx : Math.round(rawSw * seg.weight); + const vx1 = windowed ? Math.max(sx, m.tableWidth) : sx; + const vx2 = windowed ? Math.min(sx + w, canvasWidth) : sx + w; + if (!windowed || vx2 > vx1) { + d2d.fillStyle = seg.color; + d2d.roundRect(vx1, y, vx2 - vx1, barHeight, si === 0 ? 3 : 0); + d2d.fill(); + } sx += w; }); } else { @@ -1212,11 +1237,12 @@ export function renderReport( const x = dateToX(parseDate(baseline.start)) + zoom / 2; const cy = baseY + baseHeight / 2; if (!windowed || (x + baseHeight >= m.tableWidth && x - baseHeight <= canvasWidth)) { + const bcx = windowed ? Math.min(canvasWidth - baseHeight, Math.max(m.tableWidth + baseHeight, x)) : x; d2d.beginPath(); - d2d.moveTo(x, cy - baseHeight); - d2d.lineTo(x + baseHeight, cy); - d2d.lineTo(x, cy + baseHeight); - d2d.lineTo(x - baseHeight, cy); + d2d.moveTo(bcx, cy - baseHeight); + d2d.lineTo(bcx + baseHeight, cy); + d2d.lineTo(bcx, cy + baseHeight); + d2d.lineTo(bcx - baseHeight, cy); d2d.closePath(); d2d.fill(); } @@ -1249,7 +1275,9 @@ export function renderReport( // tegel geëmit), dus daar was dit nooit stuk. De omkering repareert dus feitelijk de RASTER-preview // en brengt die in lijn met wat de export altijd al deed — wat precies de bedoeling is, want die // twee horen WYSIWYG te zijn. - if (options.showDeps) { + // Bij een tijdvenster worden relaties nooit getekend: `drawDependencies` klemt niet, en het + // venster wordt alleen op het resourcediagram aangeboden, dat sowieso geen relaties tekent. + if (options.showDeps && !windowed) { // #54 volg-weergave: alleen relaties waarvan béide endpoints een zichtbare rij zijn (zelfde // regel als het scherm). rowIndexOf indexeert printRows (groepsbanden meegerekend) en is // daarmee tegelijk het zichtbaarheids- én het y-positie-bron; in boom-modus (= alle taken diff --git a/tests/planning/check-print-report.ts b/tests/planning/check-print-report.ts index f94e0df2..8e6788b9 100644 --- a/tests/planning/check-print-report.ts +++ b/tests/planning/check-print-report.ts @@ -47,6 +47,9 @@ function record(tasks: Task[], sequences: Sequence[], calendar: WorkCalendar, op const rects: RectEv[] = []; const paths: PathEv[] = []; const roundRects: RoundRectEv[] = []; + // Gevulde paden (mijlpaal- en baselineruiten, samenvattingshaakjes) — `fill()` legde ze eerder + // stil weg, waardoor een ruit die over de tabel steekt onzichtbaar bleef voor de tests. + const fills: PathEv[] = []; let seq = 0; let curPath: { x: number; y: number }[] | null = null; const st = { font: '10px x', fillStyle: '', strokeStyle: '', lineWidth: 0, textAlign: 'left' as TextAlign, textBaseline: 'alphabetic' as TextBaseline, dash: [] as number[] }; @@ -62,14 +65,14 @@ function record(tasks: Task[], sequences: Sequence[], calendar: WorkCalendar, op fillRect(x, y, w, h) { rects.push({ x, y, w, h, color: st.fillStyle, seq: seq++ }); }, strokeRect() {}, beginPath() { curPath = []; }, moveTo(x, y) { if (!curPath) curPath = []; curPath.push({ x, y }); }, lineTo(x, y) { if (!curPath) curPath = []; curPath.push({ x, y }); }, - closePath() {}, fill() { curPath = null; }, + closePath() {}, fill() { if (curPath) fills.push({ pts: curPath, color: st.fillStyle, dash: [], seq: seq++ }); curPath = null; }, stroke() { if (curPath) paths.push({ pts: curPath, color: st.strokeStyle, dash: [...st.dash], seq: seq++ }); curPath = null; }, roundRect(x, y, w, h) { roundRects.push({ x, y, w, h, color: st.fillStyle, strokeColor: st.strokeStyle, mode: 'fill', seq: seq++ }); }, fillText(text, x, y) { texts.push({ text, x, y, color: st.fillStyle, font: st.font, seq: seq++ }); }, measureText(t) { return measure(t); }, }; const dims = renderReport(() => d2d, tasks, sequences, calendar, 'P', options); - return { texts, rects, paths, roundRects, dims }; + return { texts, rects, paths, roundRects, fills, dims }; } // ── Fixtures ─────────────────────────────────────────────────────────────────────────────────── @@ -523,6 +526,24 @@ const baseOptions = (over: Partial = {}): PrintOptions => ({ ok(rec.texts.filter(t => t.text === T_HIDDEN.name && t.x < d.tableWidth).length === 1, 'taak buiten het venster houdt zijn tabelrij'); ok(JSON.stringify(record(FIX_TASKS, [], cal, fixed)) === JSON.stringify(record(FIX_TASKS, [], cal, { ...fixed, timeWindow: undefined })), 'zonder venster byte-identiek'); + + // Review-bevinding 2: een mijlpaal op de eerste vensterdag bij lage zoom (4 px/dag, ruit ± 6 px) + // hangt niet half over de tabel — het middelpunt wordt naar binnen geklemd. Niets in het + // chartgebied (gevulde paden, balken, lijnen) ligt links van de tabelrand of rechts van de chart. + const ms = mkTask('t-ms', 'Mijlpaal', { isMilestone: true, time: mkTime({ earlyStart: '2026-01-10', earlyFinish: '2026-01-10', scheduleStart: '2026-01-10', scheduleFinish: '2026-01-10' }) }); + const low = record([ms, T_CRIT], [], cal, { ...fixed, customZoom: 4, timeWindow: win }); + const ld = low.dims; + const inBodyPt = (y: number) => y >= ld.headerHeight && y < ld.height - ld.footerHeight; + const bodyFills = low.fills.filter(f => f.pts.every(p => inBodyPt(p.y))); + ok(bodyFills.length >= 1, `de ruit wordt getekend (got ${bodyFills.length} gevulde paden)`); + ok(bodyFills.every(f => f.pts.every(p => p.x >= ld.tableWidth - 1e-6 && p.x <= ld.width + 1e-6)), + `ruit binnen het chartgebied (got ${JSON.stringify(bodyFills.map(f => f.pts.map(p => Math.round(p.x * 100) / 100)))}, tabel ${ld.tableWidth}, chart tot ${ld.width})`); + const lowBars = low.roundRects.filter(r => (r.color === CRITICAL || r.color === NORMAL) && inBodyPt(r.y)); + ok(lowBars.every(b => b.x >= ld.tableWidth - 1e-6 && b.x + b.w <= ld.width + 1e-6), 'ook bij 4 px/dag blijft de balk binnen de chart'); + // Review-bevinding 7: de 3 px-minimumbreedte steekt niet meer over de rechter chartrand. + const tiny = record([T_CRIT], [], cal, { ...fixed, customZoom: 1, timeWindow: { from: '2026-01-10', to: '2026-01-12' } }); + const tinyBar = tiny.roundRects.find(r => r.color === CRITICAL && r.y >= tiny.dims.headerHeight && r.y < tiny.dims.height - tiny.dims.footerHeight); + ok(!!tinyBar && tinyBar.x + tinyBar.w <= tiny.dims.width + 1e-6 && tinyBar.w > 0, `minimumbreedte geklemd op de chartrand (got ${JSON.stringify(tinyBar)}, chart tot ${tiny.dims.width})`); } // Toewijzingskolommen (manuvarkey punt 1): twee kolommen direct achter de naam (x 180–225 en @@ -542,7 +563,7 @@ const baseOptions = (over: Partial = {}): PrintOptions => ({ const withCols = record([T_NORM, T_CRIT], [], cal, baseOptions({ rows: aBands, assignmentColumns: true, rowAssignments, curveLabels: { FRONT_LOADED: 'Vooraan belast' }, })); - ok(withCols.dims.tableWidth === plainA.dims.tableWidth + 45 + 75, `tabel precies twee kolommen breder (got +${withCols.dims.tableWidth - plainA.dims.tableWidth})`); + ok(withCols.dims.tableWidth === plainA.dims.tableWidth + 45 + 92, `tabel precies twee kolommen breder (got +${withCols.dims.tableWidth - plainA.dims.tableWidth})`); const inCol = (t: { x: number; y: number }, x0: number, x1: number) => t.x >= x0 && t.x <= x1 && t.y > withCols.dims.headerHeight && t.y < withCols.dims.height - withCols.dims.footerHeight; const unitsTexts = withCols.texts.filter(t => inCol(t, 180, 225)).map(t => t.text).sort(); const curveTexts = withCols.texts.filter(t => inCol(t, 225, 300)).map(t => t.text).sort(); diff --git a/tests/planning/check-reports.ts b/tests/planning/check-reports.ts index 847216a2..486bc602 100644 --- a/tests/planning/check-reports.ts +++ b/tests/planning/check-reports.ts @@ -491,7 +491,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); const r = computeResourceGanttRows(base, opts); eq('resourceGantt: één band per resource, op naam', r.rows.filter(x => x.kind === 'group').map(label), ['Kraan', 'Ploeg 1']); // Ploeg 1 → A, B, D; Kraan → B; zonder resource: C, E (mijlpaal is óók een bladtaak), F, G. - eq('resourceGantt: tellingen', r.counts, { resources: 2, assignments: 4, unassignedTasks: 4, outsidePeriod: 0 }); + eq('resourceGantt: tellingen', r.counts, { resources: 2, assignments: 4, unassignedTasks: 4, outsidePeriod: 0, inPeriod: 7 }); eq('resourceGantt: Ploeg 1 heeft A, B en D', [...bandTasks(r.rows, 'Ploeg 1')].sort(), [A, B, D].sort()); eq('resourceGantt: Kraan heeft alleen B', bandTasks(r.rows, 'Kraan'), [B]); ok('resourceGantt: B staat onder beide banden', r.rows.filter(x => x.kind === 'task' && x.task.id === B).length === 2); @@ -516,7 +516,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); assignments: [asg('a1', A, jan1.id), asg('a2', A, jan2.id), asg('a3', B, jan2.id)], }, opts); eq('resourceGantt: gelijknamige resources ⇒ twee banden met volgnummer', twins.rows.filter(x => x.kind === 'group').map(label), ['Jan #1', 'Jan #2']); - eq('resourceGantt: gelijknamig — tellingen op identiteit', twins.counts, { resources: 2, assignments: 3, unassignedTasks: 5, outsidePeriod: 0 }); + eq('resourceGantt: gelijknamig — tellingen op identiteit', twins.counts, { resources: 2, assignments: 3, unassignedTasks: 5, outsidePeriod: 0, inPeriod: 7 }); eq('resourceGantt: gelijknamig — Jan #1 heeft A', bandTasks(twins.rows, 'Jan #1'), [A]); eq('resourceGantt: gelijknamig — Jan #2 heeft A en B', [...bandTasks(twins.rows, 'Jan #2')].sort(), [A, B].sort()); ok('resourceGantt: gelijknamig — rijsleutels uniek', new Set(twins.rows.map(x => x.rowKey)).size === twins.rows.length); @@ -527,7 +527,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); assignments: [asg('a1', A, 'res-leeg')], }, opts); eq('resourceGantt: naamloze resource ⇒ band "#2" (positie in de projectlijst)', naamloos.rows.filter(x => x.kind === 'group').map(label), ['#2']); - eq('resourceGantt: naamloze resource — A telt als toegewezen', naamloos.counts, { resources: 1, assignments: 1, unassignedTasks: 6, outsidePeriod: 0 }); + eq('resourceGantt: naamloze resource — A telt als toegewezen', naamloos.counts, { resources: 1, assignments: 1, unassignedTasks: 6, outsidePeriod: 0, inPeriod: 7 }); // manuvarkey op #113, punt 2: twee lagen — typeband (vaste volgorde: mensen, dan materieel, dan // materiaal), daarin de resourcebanden op diepte 1, de taken op diepte 2; "(geen)" blijft achteraan @@ -567,6 +567,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); ok('resourceGantt/venster: elke getekende taak raakt het venster', windowed.rows.every(x => x.kind !== 'task' || overlapsB(x.task.id))); ok('resourceGantt/venster: elke bladtaak die het venster raakt staat er ook', leavesAll.filter(t => overlapsB(t.id)).every(t => windowed.rows.some(x => x.kind === 'task' && x.task.id === t.id))); eq('resourceGantt/venster: outsidePeriod telt de weggelaten bladtaken', windowed.counts.outsidePeriod, leavesAll.filter(t => !overlapsB(t.id)).length); + eq('resourceGantt/venster: inPeriod + outsidePeriod = alle bladtaken', windowed.counts.inPeriod + windowed.counts.outsidePeriod, leavesAll.length); ok('resourceGantt/venster: tellingen volgen de gefilterde set', windowed.counts.assignments < r.counts.assignments && windowed.counts.unassignedTasks < r.counts.unassignedTasks); const buiten = computeResourceGanttRows(base, { ...opts, window: { from: '2030-01-01', to: '2030-01-31' } }); eq('resourceGantt/venster: venster zonder taken ⇒ geen rijen, alles buiten de periode', [buiten.rows.length, buiten.counts.outsidePeriod, buiten.counts.resources], [0, leavesAll.length, 0]); @@ -576,7 +577,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); // manuvarkey op #113, punt 1: per taakrij de toewijzing van de band op die taak — eenheden opgeteld // over records van dezelfde resource, curve alleen als alle records dezelfde hebben (afwezig = // UNIFORM), anders null; taakrijen onder "(geen)" hebben geen entry. - const asgFull = (id: string, taskId: string, resourceId: string, unitsPerDay: number, curve?: ResourceCurve): ResourceAssignment => ({ id, taskId, resourceId, unitsPerDay, curve }); + const asgFull = (id: string, taskId: string, resourceId: string, unitsPerDay: number, curve?: ResourceCurve, extra: Partial = {}): ResourceAssignment => ({ id, taskId, resourceId, unitsPerDay, curve, ...extra }); const loaded = computeResourceGanttRows({ tasks: ctx.tasks, resources: [kraan, jan1], assignments: [ @@ -595,6 +596,28 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); ok('resourceGantt/toewijzing: elke taakrij onder een resourceband heeft een entry', loaded.rows.slice(0, noneStart).every(x => x.kind !== 'task' || loaded.assignmentByRowKey.has(x.rowKey))); ok('resourceGantt/toewijzing: "(geen)"-rijen hebben geen entry', noneStart > 0 && loaded.rows.slice(noneStart).every(x => x.kind !== 'task' || !loaded.assignmentByRowKey.has(x.rowKey))); eq('resourceGantt/toewijzing: aantal entries = aantal taakrijen onder resourcebanden', loaded.assignmentByRowKey.size, loaded.rows.slice(0, noneStart).filter(x => x.kind === 'task').length); + // Review-bevinding 1: dezelfde drie lagen als de lastverdeling — een contour op de taak wint, dan + // een geïmporteerde exacte curve zonder OPS-vorm, dan pas `curve`. Nooit "Uniform" bij een + // P6-/MSP-import met een echte curve. + const curveValues = [0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const dMetContour = { ...byId(D), timephasedContours: [{ resourceUid: null, resourceId: jan1.id, periods: [] }] }; + const layered = computeResourceGanttRows({ + tasks: ctx.tasks.map(t => (t.id === D ? dMetContour : t)), resources: [kraan, jan1], + assignments: [ + asgFull('c1', A, kraan.id, 1, undefined, { curveValues }), // geïmporteerd, geen OPS-vorm + asgFull('c2', B, kraan.id, 1, 'FRONT_LOADED', { curveValues }), // OPS-vorm aanwezig ⇒ die wint + asgFull('c3', D, jan1.id, 1, 'BELL'), // contour op de taak ⇒ contoured + asgFull('c4', D, kraan.id, 1, undefined, { curveValues }), // geen contour voor Kraan ⇒ imported + ], + }, opts); + const rowOf2 = (bandName: string, taskId: string) => { + const start = layered.rows.findIndex(x => label(x) === bandName); + return layered.rows.slice(start + 1).find(x => x.kind === 'task' && x.task.id === taskId)!; + }; + eq('resourceGantt/curve: curveValues zonder curve ⇒ imported', layered.assignmentByRowKey.get(rowOf2('Kraan', A).rowKey)?.curve, 'imported'); + eq('resourceGantt/curve: curveValues mét OPS-vorm ⇒ de vorm', layered.assignmentByRowKey.get(rowOf2('Kraan', B).rowKey)?.curve, 'FRONT_LOADED'); + eq('resourceGantt/curve: contour op de taak voor deze resource ⇒ contoured', layered.assignmentByRowKey.get(rowOf2('Jan', D).rowKey)?.curve, 'contoured'); + eq('resourceGantt/curve: contour geldt alleen voor de gekoppelde resource', layered.assignmentByRowKey.get(rowOf2('Kraan', D).rowKey)?.curve, 'imported'); // Twee toewijzingen van dezelfde resource op één taak zijn één rij; een toewijzing aan een // onbekende resource telt niet (die taak is dan "zonder resource", zoals op het scherm). @@ -604,7 +627,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); }, opts); eq('resourceGantt: dubbele toewijzing ⇒ één rij', bandTasks(dubbel.rows, 'Kraan'), [A]); // …maar de telling volgt de records, zoals het tabelrapport Resourcetoewijzingen (review N8). - eq('resourceGantt: onbekende resource ⇒ taak zonder resource; toewijzingen tellen records', dubbel.counts, { resources: 1, assignments: 2, unassignedTasks: 6, outsidePeriod: 0 }); + eq('resourceGantt: onbekende resource ⇒ taak zonder resource; toewijzingen tellen records', dubbel.counts, { resources: 1, assignments: 2, unassignedTasks: 6, outsidePeriod: 0, inPeriod: 7 }); // Bandvolgorde: taal-/cijferbewust ("Ploeg 2" vóór "Ploeg 10"), hoofdletterongevoelig. const p10 = res('p10', 'Ploeg 10'); const p2 = res('p2', 'ploeg 2'); const aa = res('aa', 'Aannemer'); @@ -635,7 +658,7 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); // Leeg: geen toewijzingen ⇒ geen rijen; geen taken ⇒ ook geen "(geen)"-band en nultellingen. ok('resourceGantt: geen toewijzingen ⇒ leeg', computeResourceGanttRows({ ...base, assignments: [] }, opts).rows.length === 0); const leeg = computeResourceGanttRows({ tasks: [], resources: base.resources, assignments: base.assignments }, { ...opts, includeUnassigned: true }); - eq('resourceGantt: leeg project ⇒ geen rijen, nultellingen', [leeg.rows.length, leeg.counts], [0, { resources: 0, assignments: 0, unassignedTasks: 0, outsidePeriod: 0 }]); + eq('resourceGantt: leeg project ⇒ geen rijen, nultellingen', [leeg.rows.length, leeg.counts], [0, { resources: 0, assignments: 0, unassignedTasks: 0, outsidePeriod: 0, inPeriod: 0 }]); } // ── Uitslag ────────────────────────────────────────────────────────────────────────────────────── From 8b6bbc7563b33aa03764f8dc56756c623aa5c433 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 16:32:03 +0000 Subject: [PATCH 6/7] =?UTF-8?q?Rapport:=20resourcediagram=20=E2=80=94=20re?= =?UTF-8?q?viewronde=202:=20curve-kolom=2098=20px,=20eerlijke=20curvetoest?= =?UTF-8?q?and-doc,=20contourkoppeling,=20lege-staat-wrap=20(#113)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hyperkritische review ronde 2 ("ja, mits"): - Curve-kolom 92 → 98 px: "Obciążony na początku"/"Zaimportowana krzywa" (pl) pasten op de echte Inter-metriek nog niet in 84 px netto. - De moduledoc claimde "dezelfde drie lagen als de lastverdeling"; dat is onwaar (`assignmentDayUnits` verdeelt met `curveValues` óók naast een `curve`, en negeert een contour zonder periodes). De doc zegt nu wat het is: de weergaveregel van het eigenschappenpaneel, zodat rapport en paneel gelijk lopen; een gedeelde toestandshelper is een vervolgstap. Gids nl+en: "Geïmporteerde curve" alleen als het geen van de acht eigen vormen is, en de tijdas kan bij lange namen met afkappen uit bijna verdwijnen. - Contourkoppeling op de volledige recordlijst per taak (ook records naar een onbekende resource), zoals `contourLookup` en het paneel — anders viel de legacy-terugval (`assignments.length === 1`) hier anders uit dan daar. - Lege staat: woord-wrap (`wrapWords`) i.p.v. één afgekapte regel — de Duitse `emptyPeriod` kapte bij 100 % al halverwege de instructie af. - Ruitklem alleen als het middelpunt zelf in de chart ligt (anders niet tekenen): een naar binnen geklemde baselineruit suggereerde een dag waarop de mijlpaal niet valt. - Minimumbreedte van een balk bij een venster: naar binnen geschoven zoals de ruit i.p.v. afgeknepen tot sub-pixel; zonder venster ongewijzigd. - Test: ruit-assertie eist `pts.length > 0` (was vacuüm vervulbaar), wrap-test op de lege staat, legacy-contour met een record naar een onbekende resource. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq --- public/docs/en/gids-rapporten-printen.md | 10 +++-- public/docs/nl/gids-rapporten-printen.md | 9 +++-- src/engine/reports/resourceGantt.ts | 34 ++++++++++------ src/services/print/printPreview.ts | 50 ++++++++++++++++++++---- tests/planning/check-print-report.ts | 15 +++++-- tests/planning/check-reports.ts | 9 +++++ 6 files changed, 95 insertions(+), 32 deletions(-) diff --git a/public/docs/en/gids-rapporten-printen.md b/public/docs/en/gids-rapporten-printen.md index a1a47c3b..b797740e 100644 --- a/public/docs/en/gids-rapporten-printen.md +++ b/public/docs/en/gids-rapporten-printen.md @@ -117,10 +117,12 @@ default: how many units per day the band's resource is assigned to the task, and distribution curve — "you are on Foundations half days, front loaded" says more than "you are on Foundations". A task under two bands shows each band's own load; two assignments of the same resource on one task are added up, and a dash in the curve column means those assignments carry -different curves. If the assignment has its own hour distribution or an exact curve imported from -MS Project or P6, the column says *Contour* or *Imported curve* respectively — the same as in the -properties panel. The two columns make the table wider and the time axis narrower; turn **Show -units/day and curve** off for the narrow table of the Gantt print. +different curves. If the assignment has its own hour distribution the column says *Contour*; if +the curve was imported from MS Project or P6 and is none of the eight built-in shapes it says +*Imported curve* — the same as in the properties panel. The two columns make the table a good 130 px +wider and the time axis narrower; with long task names and *Truncate task names* off the time axis +can all but disappear. Turn **Show units/day and curve** off for the narrow table of the Gantt +print. The settings of the Gantt print apply here too — critical path, float, bar colours, status line, paper, header repeat — with three exceptions: *Follow view* (this report doesn't take its rows from diff --git a/public/docs/nl/gids-rapporten-printen.md b/public/docs/nl/gids-rapporten-printen.md index 2994684f..ae6040f1 100644 --- a/public/docs/nl/gids-rapporten-printen.md +++ b/public/docs/nl/gids-rapporten-printen.md @@ -120,10 +120,11 @@ dag de resource van die band op de taak staat en met welke verdeelcurve — "je op Fundering, vooraan belast" zegt meer dan "je staat op Fundering". Een taak onder twee banden toont per band de eigen inzet; twee toewijzingen van dezelfde resource op één taak worden opgeteld, en een streepje bij de curve betekent dat die toewijzingen verschillende curves hebben. -Heeft de toewijzing een eigen urenverdeling of een uit MS Project of P6 geïmporteerde exacte curve, -dan staat er *Contour* respectievelijk *Geïmporteerde curve* — hetzelfde als in het -eigenschappenpaneel. De twee kolommen maken de tabel breder en de tijdas dus smaller; zet -**Eenheden/dag en curve tonen** uit voor de smalle tabel van de Gantt-afdruk. +Heeft de toewijzing een eigen urenverdeling, dan staat er *Contour*; is de curve uit MS Project of +P6 geïmporteerd en is het geen van de acht eigen vormen, dan staat er *Geïmporteerde curve* — +hetzelfde als in het eigenschappenpaneel. De twee kolommen maken de tabel ruim 130 px breder en de +tijdas dus smaller; bij lange taaknamen met *Taaknamen afkappen* uit kan de tijdas daardoor bijna +verdwijnen. Zet **Eenheden/dag en curve tonen** uit voor de smalle tabel van de Gantt-afdruk. De instellingen van de Gantt-afdruk gelden hier ook — kritiek pad, speling, balkkleuren, statuslijn, papier, kopherhaling — op drie na: *Volg weergave* (de rijen komen bij dit rapport niet van het diff --git a/src/engine/reports/resourceGantt.ts b/src/engine/reports/resourceGantt.ts index ab2a9eb7..e936eec1 100644 --- a/src/engine/reports/resourceGantt.ts +++ b/src/engine/reports/resourceGantt.ts @@ -46,12 +46,17 @@ import type { ResolvedPeriod } from './reportingPeriod'; * * Per taakrij onder een resourceband levert `assignmentByRowKey` de TOEWIJZING van die band op * die taak (punt 1): eenheden per dag en de verdeelcurve — de rij is een taak, maar wat de lezer - * wil weten is "hoe zwaar staat déze resource erop". De curve volgt dezelfde drie lagen als de - * lastverdeling zelf (`ResourceLoad.ts`'s `assignmentDayUnits`) en het eigenschappenpaneel - * (`TaskAssignmentsSection`): een opgeslagen CONTOUR op de taak wint (`'contoured'`), dan een - * exacte geïmporteerde curve zonder OPS-vorm (`curveValues` zonder `curve` ⇒ `'imported'`), dan - * pas `curve` (afwezig = UNIFORM) — een P6-/MSP-import met een front-loaded curve mag hier nooit - * "Uniform" heten (hyperkritische review, bevinding 1). Twee records van dezelfde resource op één + * wil weten is "hoe zwaar staat déze resource erop". De curveTOESTAND is exact de weergaveregel + * van het eigenschappenpaneel (`TaskAssignmentsSection`), zodat rapport en paneel nooit twee + * antwoorden geven: een aan de toewijzing gekoppelde CONTOUR op de taak ⇒ `'contoured'`, anders + * `curveValues` zonder OPS-vorm (`curve` afwezig) ⇒ `'imported'`, anders `curve` (afwezig = + * UNIFORM). Let op: dat is een WEERGAVEregel, niet de verdeelregel van `ResourceLoad.ts`'s + * `assignmentDayUnits` — die verdeelt met `curveValues` zodra die er zijn, óók naast een `curve` + * (het gewone P6-pad: naamterugval + exacte waarden), en negeert een contour zonder periodes. Het + * rapport toont dus wat het paneel toont ("Vooraan belast"), terwijl het histogram P6's exacte + * waarden gebruikt; een echt gedeelde toestandshelper is een vervolgstap (review ronde 2). Een + * P6-/MSP-import met een curve zonder OPS-vorm heet hier in elk geval nooit "Uniform" + * (ronde 1, bevinding 1). Twee records van dezelfde resource op één * taak (één rij) worden opgeteld; de curve is alleen bekend als alle records dezelfde hebben * (anders `null`, de render toont een streepje). De "(geen)"-band heeft geen toewijzing en dus * geen entry. De render tekent dit als twee tabelkolommen (`PrintOptions.assignmentColumns`). @@ -195,17 +200,20 @@ export function computeResourceGanttRows( // Per resource-id × taak-id de opgetelde eenheden en de verzameling curvetoestanden (punt 1). const loadByResourceTask = new Map }>(); const assignedTaskIds = new Set(); - // Geldige records per taak, voor de contourkoppeling (dezelfde `matchContoursToAssignments` als - // de lastverdeling en het eigenschappenpaneel: per taak, in recordvolgorde). - const validByTask = new Map(); + // Contourkoppeling op de VOLLEDIGE recordlijst per taak — ook records naar een onbekende + // resource (die filtert de rij-opbouw hieronder pas weg) — precies zoals `ResourceLoad.ts`'s + // `contourLookup` en het eigenschappenpaneel de lijst aanbieden; anders kan de legacy-terugval + // in `matchContoursToAssignments` (`assignments.length === 1`) hier anders uitvallen dan daar + // (review ronde 2, bevinding 3). + const recordsByTask = new Map(); for (const a of ctx.assignments) { - if (!leafById.has(a.taskId) || !resourceIds.has(a.resourceId)) continue; - const list = validByTask.get(a.taskId) ?? []; + if (!leafById.has(a.taskId)) continue; + const list = recordsByTask.get(a.taskId) ?? []; list.push(a); - validByTask.set(a.taskId, list); + recordsByTask.set(a.taskId, list); } const contouredIds = new Set(); - for (const [taskId, list] of validByTask) { + for (const [taskId, list] of recordsByTask) { const task = leafById.get(taskId)!; if (task.timephasedContours && task.timephasedContours.length > 0) { for (const id of matchContoursToAssignments(task.timephasedContours, list).keys()) contouredIds.add(id); diff --git a/src/services/print/printPreview.ts b/src/services/print/printPreview.ts index a745597a..6f96d303 100644 --- a/src/services/print/printPreview.ts +++ b/src/services/print/printPreview.ts @@ -93,7 +93,7 @@ const COL = { // Toewijzingskolommen van het resourcediagram (manuvarkey punt 1): eenheden per dag en de // verdeelcurve van de resource van de band op die taak. Alleen bij `assignmentColumns`. units: { w: 45 }, - curve: { w: 92 }, + curve: { w: 98 }, }; /** @@ -499,6 +499,23 @@ function formatDuration(days: number): string { return `${days}d`; } +/** + * Breek `text` op woordgrenzen in regels die binnen `maxWidth` passen (dezelfde px-eenheid als + * `d2d.measureText`). Eén woord dat alleen al te breed is wordt met een ellipsis afgekort. + */ +function wrapWords(d2d: Draw2D, text: string, maxWidth: number): string[] { + const lines: string[] = []; + let line = ''; + for (const word of text.split(/\s+/).filter(Boolean)) { + const candidate = line ? `${line} ${word}` : word; + if (d2d.measureText(candidate).width <= maxWidth) { line = candidate; continue; } + if (line) lines.push(line); + line = d2d.measureText(word).width <= maxWidth ? word : fitText(d2d, word, maxWidth); + } + if (line) lines.push(line); + return lines.length > 0 ? lines : ['']; +} + /** Eenheden per dag: geheel als "2", anders tot twee decimalen zonder nullen ("0.5", "1.25"). */ function formatUnitsPerDay(units: number): string { return String(Math.round(units * 100) / 100); @@ -695,7 +712,12 @@ export function renderReport( d2d.fillStyle = PRINT_COLORS.textSecondary; d2d.font = m.font(14); d2d.textAlign = 'center'; - d2d.fillText(fitText(d2d, options.labels?.noTasks ?? 'No tasks to display', 580), 300, 100); + // Woord-wrap binnen de 600 px brede doos: een instructie ("kies een andere periode …") mag + // niet halverwege afkappen (review ronde 2, bevinding 4). Verticaal is er ruimte zat. + const emptyLines = wrapWords(d2d, options.labels?.noTasks ?? 'No tasks to display', 560); + const emptyLineH = m.s(18); + const emptyTop = 100 - ((emptyLines.length - 1) * emptyLineH) / 2; + emptyLines.forEach((line, i) => d2d.fillText(line, 300, emptyTop + i * emptyLineH)); // Geen kop-/tijdschaalstrook in de lege-staat (alleen een centrale melding) ⇒ niets te herhalen. // Het meldingsvak zelf houdt z'n vaste 600×200; alleen de tekst erin volgt de schaal. return { width: 600, height: 200, tableWidth: m.tableWidth, headerHeight: 0, footerHeight: 0 }; @@ -1053,7 +1075,9 @@ export function renderReport( // Tijdvenster: een ruit die het chartgebied helemaal mist wordt niet getekend; een ruit op de // rand wordt met zijn middelpunt naar binnen geklemd, zodat hij nooit half over de tabel of // over de rechterrand hangt (hyperkritische review, bevinding 2). - const inWindow = !windowed || (x + size >= m.tableWidth && x - size <= canvasWidth); + // Middelpunt buiten het chartgebied ⇒ niet tekenen (een naar binnen geklemde ruit zou + // een dag suggereren waarop de mijlpaal niet valt — review ronde 2, bevinding 6). + const inWindow = !windowed || (x >= m.tableWidth && x <= canvasWidth); const cx = windowed ? Math.min(canvasWidth - size, Math.max(m.tableWidth + size, x)) : x; if (inWindow) { @@ -1160,8 +1184,18 @@ export function renderReport( for (const s of segs) { // De minimumbreedte (3 px, 2 bij splits) op de RUWE maat, en het einde bij een venster op de // chartrand geklemd zodat dat minimum er niet overheen steekt. - const rawSw = Math.max(s.rx2 - s.rx1, split ? 2 : 3); - const sw = (windowed ? Math.min(s.rx1 + rawSw, canvasWidth) : s.rx1 + rawSw) - s.x1; + const minW = split ? 2 : 3; + const rawSw = Math.max(s.rx2 - s.rx1, minW); + // Zichtbare breedte: zonder venster de oude `max(breedte, minimum)` op x1; bij een venster + // de geklemde breedte, en is die smaller dan het minimum, dan schuift het minimum naar + // binnen (zoals de ruit) in plaats van te worden afgeknepen, zodat een eendagstaak op de + // laatste vensterdag zichtbaar blijft (review ronde 2, bevinding 7). + let sx1 = s.x1; + let sw = windowed ? s.x2 - s.x1 : rawSw; + if (windowed && sw < minW) { + sw = Math.min(minW, canvasWidth - m.tableWidth); + sx1 = Math.max(m.tableWidth, Math.min(s.x1, canvasWidth - sw)); + } if (advies.kind === 'segments') { // Kleurvakken op de ruwe tijdas verdeeld en daarna per vak op het chartgebied geknipt: een // afgekapte balk toont zo de kleuren die bij het zichtbare stuk horen (review, bevinding 8). @@ -1180,13 +1214,13 @@ export function renderReport( }); } else { d2d.fillStyle = advies.fill; - d2d.roundRect(s.x1, y, sw, barHeight, 3); + d2d.roundRect(sx1, y, sw, barHeight, 3); d2d.fill(); } if (advies.outline) { d2d.strokeStyle = advies.outline; d2d.lineWidth = 1; - d2d.roundRect(s.x1, y, sw, barHeight, 3); + d2d.roundRect(sx1, y, sw, barHeight, 3); d2d.stroke(); } } @@ -1236,7 +1270,7 @@ export function renderReport( if (baseline.isMilestone) { const x = dateToX(parseDate(baseline.start)) + zoom / 2; const cy = baseY + baseHeight / 2; - if (!windowed || (x + baseHeight >= m.tableWidth && x - baseHeight <= canvasWidth)) { + if (!windowed || (x >= m.tableWidth && x <= canvasWidth)) { const bcx = windowed ? Math.min(canvasWidth - baseHeight, Math.max(m.tableWidth + baseHeight, x)) : x; d2d.beginPath(); d2d.moveTo(bcx, cy - baseHeight); diff --git a/tests/planning/check-print-report.ts b/tests/planning/check-print-report.ts index 8e6788b9..8f1de737 100644 --- a/tests/planning/check-print-report.ts +++ b/tests/planning/check-print-report.ts @@ -534,7 +534,9 @@ const baseOptions = (over: Partial = {}): PrintOptions => ({ const low = record([ms, T_CRIT], [], cal, { ...fixed, customZoom: 4, timeWindow: win }); const ld = low.dims; const inBodyPt = (y: number) => y >= ld.headerHeight && y < ld.height - ld.footerHeight; - const bodyFills = low.fills.filter(f => f.pts.every(p => inBodyPt(p.y))); + // `pts.length > 0`: een lege `beginPath(); roundRect(); fill()` (voortgangsoverlay) mag de + // assertie niet vacuüm vervullen (review ronde 2, bevinding 9). + const bodyFills = low.fills.filter(f => f.pts.length > 0 && f.pts.every(p => inBodyPt(p.y))); ok(bodyFills.length >= 1, `de ruit wordt getekend (got ${bodyFills.length} gevulde paden)`); ok(bodyFills.every(f => f.pts.every(p => p.x >= ld.tableWidth - 1e-6 && p.x <= ld.width + 1e-6)), `ruit binnen het chartgebied (got ${JSON.stringify(bodyFills.map(f => f.pts.map(p => Math.round(p.x * 100) / 100)))}, tabel ${ld.tableWidth}, chart tot ${ld.width})`); @@ -543,7 +545,14 @@ const baseOptions = (over: Partial = {}): PrintOptions => ({ // Review-bevinding 7: de 3 px-minimumbreedte steekt niet meer over de rechter chartrand. const tiny = record([T_CRIT], [], cal, { ...fixed, customZoom: 1, timeWindow: { from: '2026-01-10', to: '2026-01-12' } }); const tinyBar = tiny.roundRects.find(r => r.color === CRITICAL && r.y >= tiny.dims.headerHeight && r.y < tiny.dims.height - tiny.dims.footerHeight); - ok(!!tinyBar && tinyBar.x + tinyBar.w <= tiny.dims.width + 1e-6 && tinyBar.w > 0, `minimumbreedte geklemd op de chartrand (got ${JSON.stringify(tinyBar)}, chart tot ${tiny.dims.width})`); + ok(!!tinyBar && tinyBar.x + tinyBar.w <= tiny.dims.width + 1e-6 && tinyBar.w >= 3 - 1e-6, `minimumbreedte naar binnen geschoven en op de chartrand geklemd (got ${JSON.stringify(tinyBar)}, chart tot ${tiny.dims.width})`); + // Lege staat: een lange instructie wordt op woordgrenzen gewrapt, nooit halverwege afgekapt. + // De testmeter rekent 6 px per teken; deze tekst is 143 tekens = 858 px, dus minstens twee regels. + const lang = 'Keine Vorgänge im Berichtszeitraum — wählen Sie einen anderen Zeitraum oder Gesamtes Projekt, um alle Vorgänge des Projekts wieder zu sehen.'; + const leeg = record([], [], cal, baseOptions({ labels: { ...baseOptions().labels!, noTasks: lang } })); + const leegTexts = leeg.texts.map(t => t.text); + ok(leegTexts.length >= 2 && leegTexts.join(' ') === lang && leegTexts.every(t => !t.includes('…')), + `lege staat gewrapt zonder afkappen (got ${JSON.stringify(leegTexts)})`); } // Toewijzingskolommen (manuvarkey punt 1): twee kolommen direct achter de naam (x 180–225 en @@ -563,7 +572,7 @@ const baseOptions = (over: Partial = {}): PrintOptions => ({ const withCols = record([T_NORM, T_CRIT], [], cal, baseOptions({ rows: aBands, assignmentColumns: true, rowAssignments, curveLabels: { FRONT_LOADED: 'Vooraan belast' }, })); - ok(withCols.dims.tableWidth === plainA.dims.tableWidth + 45 + 92, `tabel precies twee kolommen breder (got +${withCols.dims.tableWidth - plainA.dims.tableWidth})`); + ok(withCols.dims.tableWidth === plainA.dims.tableWidth + 45 + 98, `tabel precies twee kolommen breder (got +${withCols.dims.tableWidth - plainA.dims.tableWidth})`); const inCol = (t: { x: number; y: number }, x0: number, x1: number) => t.x >= x0 && t.x <= x1 && t.y > withCols.dims.headerHeight && t.y < withCols.dims.height - withCols.dims.footerHeight; const unitsTexts = withCols.texts.filter(t => inCol(t, 180, 225)).map(t => t.text).sort(); const curveTexts = withCols.texts.filter(t => inCol(t, 225, 300)).map(t => t.text).sort(); diff --git a/tests/planning/check-reports.ts b/tests/planning/check-reports.ts index 486bc602..6f2089fd 100644 --- a/tests/planning/check-reports.ts +++ b/tests/planning/check-reports.ts @@ -618,6 +618,15 @@ eq('scenario: B rest = 6 wd (10 × 60%)', remainingDays(ctx, byId(B)), 6); eq('resourceGantt/curve: curveValues mét OPS-vorm ⇒ de vorm', layered.assignmentByRowKey.get(rowOf2('Kraan', B).rowKey)?.curve, 'FRONT_LOADED'); eq('resourceGantt/curve: contour op de taak voor deze resource ⇒ contoured', layered.assignmentByRowKey.get(rowOf2('Jan', D).rowKey)?.curve, 'contoured'); eq('resourceGantt/curve: contour geldt alleen voor de gekoppelde resource', layered.assignmentByRowKey.get(rowOf2('Kraan', D).rowKey)?.curve, 'imported'); + // Ronde 2, bevinding 3: de contourkoppeling loopt op de VOLLEDIGE recordlijst van de taak, ook + // met een record naar een onbekende resource erbij — dan slaat de legacy-terugval (één contour + // zonder resourceId bij precies één record) níét toe, net als in het paneel en de lastverdeling. + const dLegacy = { ...byId(D), timephasedContours: [{ resourceUid: null, periods: [] }] }; + const legacy = computeResourceGanttRows({ + tasks: ctx.tasks.map(t => (t.id === D ? dLegacy : t)), resources: [kraan], + assignments: [asgFull('g1', D, kraan.id, 1), asgFull('g2', D, 'res-onbekend', 1)], + }, opts); + eq('resourceGantt/curve: legacy-contour telt niet bij twee records (ook als er één naar een onbekende resource wijst)', legacy.assignmentByRowKey.get(legacy.rows.find(x => x.kind === 'task' && x.task.id === D)!.rowKey)?.curve, 'UNIFORM'); // Twee toewijzingen van dezelfde resource op één taak zijn één rij; een toewijzing aan een // onbekende resource telt niet (die taak is dan "zonder resource", zoals op het scherm). From 43e37226af02d13132303679393e869abee4689f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 16:45:36 +0000 Subject: [PATCH 7/7] test(browser): just-updated-dialog stubt de GitHub Releases-API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit De twee updater-specs faalden op CI (run 34869219752) op een 403 van api.github.com — de onbevoegde Releases-API is op een gedeelde runner rate-limited — en in een sandbox achter een proxy op ERR_CERT_AUTHORITY_INVALID; beide als console.error, die de harness als fout telt. De tests asserteren niets uit die vergelijking (de pure functies staan in tests/planning/check-just-updated.ts), dus de route antwoordt nu deterministisch met een lege lijst. Lokaal 2/2 groen, waar dezelfde spec eerder in deze sandbox rood stond. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq --- tests/browser/just-updated-dialog.spec.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/browser/just-updated-dialog.spec.ts b/tests/browser/just-updated-dialog.spec.ts index c0724734..d1d22c85 100644 --- a/tests/browser/just-updated-dialog.spec.ts +++ b/tests/browser/just-updated-dialog.spec.ts @@ -7,6 +7,18 @@ declare global { } } +/** + * De dialoog bevraagt de GitHub Releases-API voor de releasevergelijking (grootteverschil, tijd + * tussen releases). Onbevoegd is die API op een gedeelde CI-runner rate-limited (403) en achter + * een sandboxproxy niet vertrouwd (ERR_CERT_AUTHORITY_INVALID); beide geven een console.error die + * de harness als fout telt, terwijl deze tests niets uit die vergelijking asserteren. Antwoord + * daarom deterministisch met een lege lijst — de pure functies erachter zijn headless getest in + * tests/planning/check-just-updated.ts. + */ +async function stubReleasesApi(page: Page): Promise { + await page.route('https://api.github.com/repos/**', route => route.fulfill({ status: 200, contentType: 'application/json', body: '[]' })); +} + async function selectLocale(page: Page, option: string, expectedLocale: string): Promise { await page.evaluate(() => window.__OPS__!.store.getState().setUI({ showSettingsDialog: true })); const settings = page.locator('.settings-dialog'); @@ -21,6 +33,7 @@ async function selectLocale(page: Page, option: string, expectedLocale: string): } test('update-highlights volgen de app-ready route en houden externe link open', async ({ page, ops: _ops }) => { + await stubReleasesApi(page); await page.evaluate(() => { window.openedReleaseUrls = []; window.open = ((url?: string | URL) => { @@ -50,6 +63,7 @@ test('update-highlights volgen de app-ready route en houden externe link open', }); test('update-highlights werken smal, licht/donker en RTL', async ({ page, ops: _ops }) => { + await stubReleasesApi(page); await page.setViewportSize({ width: 390, height: 420 }); await selectLocale(page, 'NL — Nederlands', 'nl'); await page.evaluate(() => window.__OPS__!.store.getState().setUI({ uiTheme: 'light', justUpdated: { from: null, to: '2026.8.1' } }));