@@ -74,6 +74,7 @@ public string GenerateIndex(ExtractedProject project, IReadOnlyList<string> deta
7474 WriteCriteriaExamples ( sb , conventions ) ;
7575 WriteEditors ( sb , project ) ;
7676 WriteMigrations ( sb , project ) ;
77+ WriteReports ( sb , project ) ;
7778 WriteConventions ( sb , project , conventions ) ;
7879 WriteRecipes ( sb , project , conventions ) ;
7980 WriteDetailPointers ( sb , detailFiles ) ;
@@ -547,6 +548,67 @@ private static void WriteEditors(StringBuilder sb, ExtractedProject project)
547548 /// column holds what it holds, an agent will reason from code that runs today and invent a
548549 /// cause. The real one ran once, years ago, and only the updater remembers it.
549550 /// </remarks>
551+ /// <summary>
552+ /// The reports, and the reason their number may be larger than it looks.
553+ /// </summary>
554+ /// <remarks>
555+ /// The bound is the point of this block, not the list. An agent that reads "no reports" builds
556+ /// as though none exist, and with <c>ReportsModuleV2</c> registered that is the normal state of
557+ /// an application whose users design their reports at run time — stored as database rows, which
558+ /// nothing reading source can reach. The list is worth having; the sentence is worth more.
559+ /// </remarks>
560+ private static void WriteReports ( StringBuilder sb , ExtractedProject project )
561+ {
562+ if ( ! project . ReferencesReportsModule && project . Reports . Count == 0 )
563+ return ;
564+
565+ sb . AppendLine ( "## Reports" ) ;
566+ sb . AppendLine ( ) ;
567+
568+ if ( project . Reports . Count == 0 )
569+ {
570+ sb . AppendLine ( "This application registers `ReportsModuleV2` and declares **no report in source**." ) ;
571+ sb . AppendLine ( "That is not the same as having none: users design reports at run time and they are" ) ;
572+ sb . AppendLine ( "stored in the database, out of reach of anything reading this repository. The number" ) ;
573+ sb . AppendLine ( "is unknown, not zero — do not tell anyone this application has no reports." ) ;
574+ sb . AppendLine ( ) ;
575+
576+ return ;
577+ }
578+
579+ foreach ( var report in project . Reports )
580+ {
581+ var opens = report . ParametersType is { Length : > 0 } parameters
582+ ? $ ", after a `{ parameters } ` dialog"
583+ : "" ;
584+
585+ sb . AppendLine ( $ "- **{ report . DisplayName } ** — over `{ report . DataType } `{ opens } "
586+ + FilterClause ( report ) ) ;
587+ }
588+
589+ sb . AppendLine ( ) ;
590+
591+ if ( project . ReferencesReportsModule )
592+ {
593+ sb . AppendLine ( "`ReportsModuleV2` is registered, so users can also design reports at run time." ) ;
594+ sb . AppendLine ( "Those live in the database and are not listed here: **this is a lower bound**." ) ;
595+ sb . AppendLine ( ) ;
596+ }
597+
598+ if ( project . UnregisteredReportLayouts . Count > 0 )
599+ {
600+ sb . AppendLine ( $ "{ project . UnregisteredReportLayouts . Count } more layout"
601+ + ( project . UnregisteredReportLayouts . Count == 1 ? " is" : "s are" )
602+ + " in the repository that no registration names — usually exported from the running" ) ;
603+ sb . AppendLine ( "application and imported by hand. Ask `xaf_reports` for them." ) ;
604+ sb . AppendLine ( ) ;
605+ }
606+ }
607+
608+ /// <summary>The filter a report carries, which is the business decision inside it.</summary>
609+ private static string FilterClause ( ExtractedReport report ) =>
610+ report . Layout ? . FilterString is { Length : > 0 } filter ? $ ", filtered to `{ filter } `" : "" ;
611+
550612 private static void WriteMigrations ( StringBuilder sb , ExtractedProject project )
551613 {
552614 if ( project . Migrations . Count == 0 )
0 commit comments