@@ -361,11 +361,26 @@ pub fn summary_fields(report: &Report) -> Vec<SummaryFieldDef> {
361361 "CrystalDecisions.CrystalReports.Engine.DatabaseFieldDefinition"
362362 }
363363 . to_string ( ) ;
364+ // The Operation is the base aggregation. A percentage summary leads with `PercentOf<Op>`
365+ // but its Operation is the underlying op (`Sum`) — percentage is a display mode, not a
366+ // distinct SummaryOperation.
367+ let op_token = p. ds . split ( " (" ) . next ( ) . unwrap_or ( "" ) . trim ( ) ;
368+ let operation = op_token
369+ . strip_prefix ( "PercentOf" )
370+ . unwrap_or ( op_token)
371+ . to_string ( ) ;
372+ // Maximum / Minimum return the summarized field's own type (the result is one of the
373+ // input values), unlike Sum/Average which coerce to Number/Currency. Use the summarized
374+ // DB field's declared type (the placed object's value_type reports the coerced Number).
375+ let vt = match operation. as_str ( ) {
376+ "Maximum" | "Minimum" => summarized_db_field_type ( & groups, report) . unwrap_or ( p. vt ) ,
377+ _ => p. vt ,
378+ } ;
364379 SummaryFieldDef {
365380 formula_name : p. ds . to_string ( ) ,
366- operation : p . ds . split ( " (" ) . next ( ) . unwrap_or ( "" ) . trim ( ) . to_string ( ) ,
367- value_type : format ! ( "{:?}Field" , p . vt ) ,
368- number_of_bytes : summary_number_of_bytes ( p . vt , & groups, report) ,
381+ operation,
382+ value_type : format ! ( "{vt :?}Field" ) ,
383+ number_of_bytes : summary_number_of_bytes ( vt, & groups, report) ,
369384 summarized_field_type,
370385 grouped : !p. scope . is_empty ( ) ,
371386 named : p. named ,
@@ -393,6 +408,22 @@ fn brace_groups(s: &str) -> Vec<&str> {
393408 out
394409}
395410
411+ /// The declared type of the summary's summarized field (`groups[0]`, e.g. `{Command.days}`) when it
412+ /// is a database field; `None` for a formula operand or an unresolved field.
413+ fn summarized_db_field_type ( groups : & [ & str ] , report : & Report ) -> Option < FieldValueType > {
414+ let name = groups
415+ . first ( ) ?
416+ . trim_start_matches ( '{' )
417+ . trim_end_matches ( '}' ) ;
418+ report
419+ . database
420+ . tables
421+ . iter ( )
422+ . flat_map ( |t| & t. data_fields )
423+ . find ( |f| f. long_name . as_deref ( ) . unwrap_or ( & f. name ) == name)
424+ . map ( |f| f. value_type )
425+ }
426+
396427/// `NumberOfBytes` for a summary's result type: the intrinsic width, except a string result uses the
397428/// summarized field's declared length (the result record's length byte is unreliable for strings).
398429fn summary_number_of_bytes ( vt : FieldValueType , groups : & [ & str ] , report : & Report ) -> i32 {
@@ -537,7 +568,55 @@ fn count_report(counts: &mut HashMap<String, i32>, field_refs: &[String], r: &Re
537568 for g in & r. data_definition . groups {
538569 let key = format ! ( "{{{}}}" , g. condition_field) ;
539570 if field_refs. contains ( & key) {
540- * counts. entry ( key) . or_default ( ) += 1 ;
571+ // A group registers its DB condition field twice: as the group condition and as the
572+ // group sort (both from the same `0xe5`). The sort is normally counted via `record_sorts`
573+ // below. A Top N / Bottom N group sorts by a summary expression instead, so its
574+ // `record_sorts` entry no longer names the field — count the sort reference here when the
575+ // sort field differs.
576+ * counts. entry ( key. clone ( ) ) . or_default ( ) += 1 ;
577+ // A summary-sorted group holds, on its condition field, the group-sort reference plus one
578+ // reference per group-scoped construct: the group-name definition, each placed GroupName
579+ // field object, and each group summary whose group-by (2nd) arg is this field. A plain
580+ // group does not reach here.
581+ if g. sort . field != g. condition_field {
582+ // The group-sort reference held on the condition field (condition + sort).
583+ * counts. entry ( key. clone ( ) ) . or_default ( ) += 1 ;
584+ * counts. entry ( key. clone ( ) ) . or_default ( ) += 1 ; // GroupName definition
585+ let gn_ds = format ! ( "GroupName ({key})" ) ;
586+ let placed_group_names = r
587+ . report_definition
588+ . areas
589+ . iter ( )
590+ . flat_map ( |a| & a. sections )
591+ . flat_map ( |s| & s. objects )
592+ . filter (
593+ |o| matches ! ( & o. kind, ReportObjectKind :: Field ( f) if f. data_source == gn_ds) ,
594+ )
595+ . count ( ) ;
596+ let summary_suffix = format ! ( ", {key})" ) ;
597+ let group_summaries = summary_fields ( r)
598+ . iter ( )
599+ . filter ( |s| s. formula_name . ends_with ( & summary_suffix) )
600+ . count ( ) ;
601+ * counts. entry ( key) . or_default ( ) += ( placed_group_names + group_summaries) as i32 ;
602+
603+ // The Top N sort basis is a summary (`g.sort.field`) referencing its summarized
604+ // (1st-arg) field. If the group displays that same summary it is already counted via
605+ // `summary_fields`; if it displays a different one (e.g. a percentage, while the sort
606+ // basis stays a plain `Sum`), that sort-basis summary is a separate, otherwise
607+ // uncounted reference → +1.
608+ let sort_summary = g. sort . field . as_str ( ) ;
609+ let already_counted = summary_fields ( r)
610+ . iter ( )
611+ . any ( |s| s. formula_name == sort_summary) ;
612+ if !already_counted {
613+ if let Some ( first) = brace_groups ( sort_summary) . into_iter ( ) . next ( ) {
614+ if let Some ( fr) = field_refs. iter ( ) . find ( |fr| fr. as_str ( ) == first) {
615+ * counts. entry ( fr. clone ( ) ) . or_default ( ) += 1 ;
616+ }
617+ }
618+ }
619+ }
541620 }
542621 }
543622 for s in & r. data_definition . record_sorts {
@@ -633,7 +712,14 @@ fn count_report(counts: &mut HashMap<String, i32>, field_refs: &[String], r: &Re
633712 if let Some ( g) = r. data_definition . groups . first ( ) {
634713 let key = format ! ( "{{{}}}" , g. condition_field) ;
635714 if field_refs. contains ( & key) {
636- * counts. entry ( key) . or_default ( ) += 1 ;
715+ // A reused-group chart adds +1 over a plain group. On a summary-sorted
716+ // group it instead binds a full category (condition + sort) → +2.
717+ let n = if g. sort . field != g. condition_field {
718+ 2
719+ } else {
720+ 1
721+ } ;
722+ * counts. entry ( key) . or_default ( ) += n;
637723 }
638724 }
639725 }
0 commit comments