reportdesign/qa/unit/ReportDesignBasicTests.cxx                 |   57 
++++++++++
 reportdesign/qa/unit/data/ReportBuilder_grouping_same_field.odb |binary
 reportdesign/source/filter/xml/xmlExport.cxx                    |    4 
 reportdesign/source/filter/xml/xmlGroup.cxx                     |   31 ++++-
 4 files changed, 88 insertions(+), 4 deletions(-)

New commits:
commit e348d752613dc30e33a48e26865ac195a4b4991b
Author:     Vissarion Fisikopoulos <[email protected]>
AuthorDate: Wed Nov 12 15:42:27 2025 +0200
Commit:     Hossein <[email protected]>
CommitDate: Mon Nov 24 22:10:29 2025 +0100

    tdf#62248 fix multiple groupings on same field issue in reportbuilder
    
    In a reportbuilder report, when grouping on the same field multiple times
    (with different interval/prefix characters setting), the two groupings 
interact
    and in the end it does not work right; both grouping levels behave like one 
of
    them.
    
    That's because the report function created by Report Builder for each of the
    groupings share the same name. Choose unique function name in
    ORptExport::exportGroupsExpressionAsFunction and sync OXMLGroup::OXMLGroup 
to
    parse that name. Keep supporting legacy names for backwards compatibility.
    
    Change-Id: Ic91e51743a407225d50b14f85d02c58ca0a243c0
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193993
    Tested-by: Jenkins
    Reviewed-by: Hossein <[email protected]>

diff --git a/reportdesign/qa/unit/ReportDesignBasicTests.cxx 
b/reportdesign/qa/unit/ReportDesignBasicTests.cxx
index 03489b24501b..875aca028be9 100644
--- a/reportdesign/qa/unit/ReportDesignBasicTests.cxx
+++ b/reportdesign/qa/unit/ReportDesignBasicTests.cxx
@@ -21,6 +21,7 @@
 
 #include <com/sun/star/sdb/application/XDatabaseDocumentUI.hpp>
 #include <comphelper/namedvaluecollection.hxx>
+#include <com/sun/star/text/XTextDocument.hpp>
 
 using namespace ::com::sun::star;
 using namespace ::com::sun::star::uno;
@@ -58,6 +59,62 @@ CPPUNIT_TEST_FIXTURE(RptBasicTest, roundTripTest)
     testLoadingAndSaving(u"calc8"_ustr, aReportNames[1], xComponentLoader, 
xActiveConnection);
 }
 
+CPPUNIT_TEST_FIXTURE(RptBasicTest, multiGroupingSameFieldIntervals)
+{
+    // ODB fixture must contain a table and a report that groups on the same 
field twice
+    // with different settings (e.g., INTERVAL 5 and INTERVAL 10). The report 
should be
+    // prepared to reproduce the original failure (Column not found: 
INT_count_...).
+    loadURLCopy(u"ReportBuilder_grouping_same_field.odb");
+
+    Reference<frame::XModel> xModel(mxComponent, UNO_QUERY_THROW);
+    Reference<frame::XController> xController(xModel->getCurrentController());
+    Reference<sdb::application::XDatabaseDocumentUI> xUI(xController, 
UNO_QUERY_THROW);
+
+    xUI->connect();
+    Reference<XConnection> xActiveConnection = xUI->getActiveConnection();
+
+    Reference<XReportDocumentsSupplier> xSupp(xModel, UNO_QUERY_THROW);
+    Reference<container::XNameAccess> xNameAccess = 
xSupp->getReportDocuments();
+    const Sequence<OUString> aReportNames(xNameAccess->getElementNames());
+    CPPUNIT_ASSERT(aReportNames.getLength() > 0);
+
+    Reference<frame::XComponentLoader> xComponentLoader(xNameAccess, 
UNO_QUERY_THROW);
+
+    // Execute all reports via writer export. This will fail the test if any 
report
+    // throws ReportExecutionException like "Column not found: 
INT_count_Number_*".
+    for (const OUString& rName : aReportNames)
+        testLoadingAndSaving(u"writer8"_ustr, rName, xComponentLoader, 
xActiveConnection);
+
+    // Additionally, load the textual outputs of all reports and compare their 
lengths.
+    // They should be identical.
+    if (aReportNames.getLength() >= 2)
+    {
+        ::comphelper::NamedValueCollection aLoadArgs;
+        aLoadArgs.put(u"ActiveConnection"_ustr, xActiveConnection);
+
+        Reference<lang::XComponent> xComp1 = 
xComponentLoader->loadComponentFromURL(
+            aReportNames[0], u"_blank"_ustr, 0, aLoadArgs.getPropertyValues());
+        Reference<lang::XComponent> xComp2 = 
xComponentLoader->loadComponentFromURL(
+            aReportNames[1], u"_blank"_ustr, 0, aLoadArgs.getPropertyValues());
+        Reference<text::XTextDocument> xTextDoc1(xComp1, UNO_QUERY);
+        Reference<text::XTextDocument> xTextDoc2(xComp2, UNO_QUERY);
+
+        if (xTextDoc1.is() && xTextDoc2.is())
+        {
+            sal_Int32 nLen1 = xTextDoc1->getText()->getString().getLength();
+            sal_Int32 nLen2 = xTextDoc2->getText()->getString().getLength();
+            CPPUNIT_ASSERT_EQUAL_MESSAGE("Report textual output length 
differs", nLen1, nLen2);
+        }
+
+        Reference<util::XCloseable> xClose1(xComp1, UNO_QUERY);
+        if (xClose1.is())
+            xClose1->close(true);
+        Reference<util::XCloseable> xClose2(xComp2, UNO_QUERY);
+        if (xClose2.is())
+            xClose2->close(true);
+    }
+}
+
 void RptBasicTest::testLoadingAndSaving(const OUString& rFilterName, const 
OUString& rReportName,
                                         Reference<frame::XComponentLoader>& 
xComponentLoader,
                                         Reference<XConnection>& 
xActiveConnection)
diff --git a/reportdesign/qa/unit/data/ReportBuilder_grouping_same_field.odb 
b/reportdesign/qa/unit/data/ReportBuilder_grouping_same_field.odb
new file mode 100644
index 000000000000..bba06e5aa285
Binary files /dev/null and 
b/reportdesign/qa/unit/data/ReportBuilder_grouping_same_field.odb differ
diff --git a/reportdesign/source/filter/xml/xmlExport.cxx 
b/reportdesign/source/filter/xml/xmlExport.cxx
index 455b46648769..7d09758dbcbf 100644
--- a/reportdesign/source/filter/xml/xmlExport.cxx
+++ b/reportdesign/source/filter/xml/xmlExport.cxx
@@ -1415,7 +1415,9 @@ void ORptExport::exportGroupsExpressionAsFunction(const 
Reference< XGroups>& _xG
                         sFunction = "INT";
                         uno::Reference< XFunction> xCountFunction = 
xFunctions->createFunction();
                         xCountFunction->setInitialFormula(beans::Optional< 
OUString>(true,u"rpt:0"_ustr));
-                        OUString sCountName = sFunction + "_count_" + 
sExpression;
+                        OUString sCountName = sFunction + "_count_"
+                            + OUString::number(xGroup->getGroupInterval())
+                            + "_" + sExpression;
                         xCountFunction->setName(sCountName);
                         xCountFunction->setFormula( "rpt:[" + sCountName + "] 
+ 1" );
                         exportFunction(xCountFunction);
diff --git a/reportdesign/source/filter/xml/xmlGroup.cxx 
b/reportdesign/source/filter/xml/xmlGroup.cxx
index b25a31fa8089..a3799c19905e 100644
--- a/reportdesign/source/filter/xml/xmlGroup.cxx
+++ b/reportdesign/source/filter/xml/xmlGroup.cxx
@@ -73,6 +73,14 @@ OXMLGroup::OXMLGroup( ORptFilter& _rImport
                 case XML_ELEMENT(REPORT, XML_SORT_ASCENDING):
                     m_xGroup->setSortAscending(IsXMLToken(aIter, XML_TRUE));
                     break;
+                case XML_ELEMENT(REPORT, XML_SORT_EXPRESSION):
+                    {
+                        // Use the explicit sort field as the group's 
expression
+                        OUString sExpr = aIter.toString();
+                        if (sExpr.indexOf("INT_count") == -1)// for backward 
compatibility
+                            m_xGroup->setExpression(sExpr);
+                    }
+                    break;
                 case XML_ELEMENT(REPORT, XML_GROUP_EXPRESSION):
                     {
                         OUString sValue = aIter.toString();
@@ -138,7 +146,21 @@ OXMLGroup::OXMLGroup( ORptFilter& _rImport
                                 {
                                     nGroupOn = report::GroupOn::INTERVAL;
                                     _rImport.removeFunction(sExpression);
-                                    sExpression = 
sExpression.copy(std::string_view("INT_count_").size());
+                                    // sExpression can be either:
+                                    //  - "INT_count_<digits>_<Field>" (new)
+                                    //  - "INT_count_<Field>"          (legacy)
+                                    const OUString aPrefix = 
u"INT_count_"_ustr;
+                                    const sal_Int32 nStart = 
aPrefix.getLength();
+                                    const sal_Int32 nEnd = 
sExpression.indexOf(u'_', nStart);
+                                    const sal_Int32 nDigitsOfInterval = (nEnd 
>= 0 && nEnd > nStart)
+                                        ? (nEnd - nStart) : 0;
+
+                                    // Cut after prefix; if digits found, also 
skip the following '_'
+                                    sal_Int32 nCut = nStart;
+                                    if (nDigitsOfInterval > 0 && nEnd >= 0)
+                                        nCut = nEnd + 1;
+                                    if (nCut <= sExpression.getLength())
+                                        sExpression = sExpression.copy(nCut);
                                     OUString sInterval = 
sCompleteFormula.getToken(1,'/');
                                     sInterval = sInterval.getToken(0,')');
                                     
m_xGroup->setGroupInterval(sInterval.toInt32());
@@ -146,10 +168,13 @@ OXMLGroup::OXMLGroup( ORptFilter& _rImport
 
                                 m_xGroup->setGroupOn(nGroupOn);
 
+                                // Drop only the reference name; keep 
functions for export
                                 _rImport.removeFunction(sValue);
-                                sValue = sExpression;
+
+                                // Only set expression from fallback if not 
already provided via sort-expression
+                                if (m_xGroup->getExpression().isEmpty())
+                                    m_xGroup->setExpression(sExpression);
                             }
-                            m_xGroup->setExpression(sValue);
                         }
                     }
                     break;

Reply via email to