5
votes

Scenario:

I've two reports: Main Report (let's call it, A) and sub-report (let's call it, B).

Report A contains sub-report B at the detail band, so sub-report B is displayed for each element at the Report A datasource. Sub-report B also returns a variable to the Main report A.

What I want is to sum those return values from sub-report B and totalize them at the Main report summary.

To do that, I have tried to create a new report variable that sum those returns values... Something like this:

Variable Definition Example

However, I've found that such variables expression are always evaluated before the band detail is rendered, so I always miss the first sub-report return value...

Sadly, the evaluation time (as this link says) cannot be changed on those kind of variables, so I'm stuck...

2
I have the same problem, except that I miss not only the first sub-report return value.Gustavo

2 Answers

2
votes

After been struggling with this for some hours... and searching the internet for a solution... I came with a Workaround (the enlightening forums were these ones: one and two).

First, you need to define a java Class Helper that allows you calculate some arithmetic operation, in my case a Sum operation. I defined these classes:

package reports.utils;

import java.util.Map;

/**
 * Utility that allows you to sum Integer values.
 */
public class SumCalculator {

    /**
     * Stores a map of {@code SumCalculator} instances (A Map instance per thread).
     */
    private static final ThreadLocalMap<String, SumCalculator> calculatorsIndex = new ThreadLocalMap<>();

    /**
     * The sum total.
     */
    private int total = 0;


    /**
     * No arguments class constructor.
     */
    private SumCalculator() {
        super();
    }


    /**
     * Instance a new {@code SumCalculator} with the given ID.
     *
     * @param id    {@code SumCalculator}'s ID
     * @return      the new {@code SumCalculator} instance
     */
    public static SumCalculator get(String id) {
        Map<String, SumCalculator> map = calculatorsIndex.get();
        SumCalculator calculator       = map.get(id);

        if (calculator == null) {
            calculator = new SumCalculator();
            map.put(id, calculator);
        }
        return calculator;
    }


    /**
     * Destroy the {@code SumCalculator} associated to the given ID.
     *
     * @param id    {@code SumCalculator}'s ID
     * @return      {@code null}
     */
    public static String destroy(String id) {
        Map<String, SumCalculator> map;

        map = calculatorsIndex.get();
        map.remove(id);

        if (map.isEmpty()) {
            calculatorsIndex.remove();
        }
        return null;
    }


    /**
     * Resets the {@code SumCalculator} total.
     *
     * @return  {@code null}
     */
    public String reset() {
        total = 0;
        return null;
    }


    /**
     * Adds the given integer value to the accumulated total.
     *
     * @param i     an integer value (can be null)
     * @return      {@code null}
     */
    public String add(Integer i) {
        this.total += (i != null) ? i.intValue() : 0;
        return null;
    }


    /**
     * Return the accumulated total.
     *
     * @return  an Integer value (won't be null, never!)
     */
    public Integer getTotal() {
        return this.total;
    }
}

package reports.utils;

import java.util.HashMap;
import java.util.Map;

/**
 * Thread Local variable that holds a {@code java.util.Map}.
 */
class ThreadLocalMap<K, V> extends ThreadLocal<Map<K, V>> {

    /**
     * Class Constructor.
     */
    public ThreadLocalMap() {
        super();
    }


    /* (non-Javadoc)
     * @see java.lang.ThreadLocal#initialValue()
     */
    @Override
    protected Map<K, V> initialValue() {
        return new HashMap<>();
    }
}

Second, at your jasper report, you need to define four text fields:

1) A text field that iniatializes your calculator; it should be (ideally) at the title section of the report and should have an expression like this: SumCalculator.get("$V{SUB_REPORT_RETURN_VALUE}").reset(). This text field should have the evaluation time: NOW.

2) A text field that calls the increment function (i.e. SumCalculator.get("$V{SUB_REPORT_RETURN_VALUE}").add($V{SUB_REPORT_RETURN_VALUE}). This text field will reside at your detail band, after the subreport element; and it should have the evaluation time: BAND (this is very important!!)

3) A text field that prints the calculator total. This text field will reside at your summary band, it will evaluate to NOW. Its expression will be: SumCalculator.get("$V{SUB_REPORT_RETURN_VALUE}").getTotal()

4) A text field that destroy the calculator. This text field will also reside at your summary band and must appear after the text field 3. The text field should have an expression like: SumCalculator.destroy("$V{SUB_REPORT_RETURN_VALUE}"). This text field should have the evaluation time: NOW.

Also, the text fields: 1, 2, and 4, should have the attribute "Blank when Null", so they will never be printed (that's why those java operations always return null).

And That's it. Then, your report can look something like this:

reportExample

0
votes

if i understand the problem, you can not summarize the amount returned by the sub report in the main report, i had the same problem and i solved in this way.

1.- Create a class which extends from net.sf.jasperreports.engine.JRDefaultScriptlet. and override the method beforeReportInit()

this is the code from this class.

package com.mem.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.jasperreports.engine.JRDefaultScriptlet;

public class SumarizacionSubtotales extends JRDefaultScriptlet {
    private final Log log = LogFactory.getLog(getClass());

    private Double total;

    public Double getTotal() {
        return total;
    }

    public Double add(Double cantidad) {
        if(log.isDebugEnabled())log.debug("AGREGANDO LA CANTIDAD : " + cantidad);
        this.total += cantidad;
        return cantidad;
    }
    @Override
    public void beforeReportInit() throws JRScriptletException {
        if(log.isDebugEnabled())log.debug("beforeReportInit");
       total = 0.0D;
    }
}

2.- add your project's jar in your ireport's classpath. ireport classpath config.

3.- Replace the class of the REPORT scriptlet. default REPORT scriptlet

in the properties with your class. your class

3.- add in the group footer where you want to print the value returned by the sub-report a textfield with the following expression.

$P{REPORT_SCRIPTLET}.add( $V{sum_detalles} )

In this case $V{sum_detalles} is a variable in the main report which contains the value returned by the sub-report.

4.- Add in the Last page footer another textfield with the following expression.

$P{REPORT_SCRIPTLET}.getTotal()

report structure