Skip to content

Number Voucher Lines per Subsidiary and Fiscal Year ​

This Groovy script gives each line of a receipt or payment voucher a running number that counts that line's subsidiary — the customer, supplier or employee on the line — within the fiscal year. The third payment to supplier S0001 this year is number 3, whatever voucher it is on.

Customer-specific example

This script works, but it is hard-wired to one setup. It overwrites the line's N1 and Narration, and the text it writes is fixed as <subsidiary code> - <fiscal year code> - <number>. If those fields are used for something else, or the text should look different, adapt the script before you use it.

Class nameEACreateSerialForLineSubsidiary
ScriptEACreateSerialForLineSubsidiary.groovy
Runs onReceipt Voucher or Payment Voucher, on Validate On Save
UsesNo parameters. Leave Parameter 1 empty.
Shipped asSource to paste. It is not built into the system.

When to use it ​

Some companies number their payments to each party, so that a supplier can quote "payment 2026 - 14" and both sides know which one is meant. The voucher code cannot do this, because it counts every voucher of the book, not the payments to one party. This script keeps that per-party count on the lines themselves.

How it works ​

  1. It groups the lines by subsidiary. Lines with no subsidiary are left alone.
  2. It finds where the count stands. For each subsidiary it looks for the latest committed voucher of the same type (payment vouchers for a payment voucher, receipt vouchers for a receipt voucher) in the same fiscal year that has a line for that subsidiary. "Latest" means the latest value date; on the same date, the one created last. Vouchers dated after the one being saved do not count. The last such line's N1 is the count so far, or 0 when there is none.
  3. It numbers the lines. Each line of that subsidiary, in the order of the grid, gets the next number in N1, and the text <subsidiary code> - <fiscal year code> - <number> replaces its Narration.

The search covers every book and every legal entity, and nothing stores the numbers apart from the lines. So the numbers are recalculated every time the voucher is saved. Saving or deleting an older voucher does not renumber the vouchers after it.

Setting it up ​

  1. Open Administration → Display Customization → Entity Flow and create a new flow with Target Type set to Payment Voucher (or Receipt Voucher).
  2. Add a line to the Details grid with Target Action set to Validate On Save. Paste the whole script from the file above into Groovy Script, and leave Class Name empty: it is filled in when you save.
  3. Save the flow. Class Name shows the script's class name, and the description appears in Description.
ParameterTitle shown after savingWhat to enter
Parameter 1Not used - leave emptyNothing.

Example ​

A payment voucher in fiscal year 2026 pays supplier S0001 on two lines and posts a third line to an expense account with no subsidiary. No earlier voucher this year carries a number for S0001.

LineSubsidiaryN1Narration after saving
1S00011S0001 - 2026 - 1
2(none)unchanged
3S00012S0001 - 2026 - 2

The next payment voucher to S0001 continues the count: its line gets N1 3 and the narration S0001 - 2026 - 3.

The script ​

groovy
package com.namasoft.modules.accounting.domain.utils.actions

import com.namasoft.common.fieldids.AllModulesFieldIDs
import com.namasoft.common.fieldids.NotGeneratedFields
import com.namasoft.common.utilities.CollectionsUtility
import com.namasoft.common.utilities.ObjectChecker
import com.namasoft.infra.domainbase.common.PageData
import com.namasoft.infra.domainbase.common.criteria.CriteriaBuilder
import com.namasoft.infra.domainbase.datafields.DecimalDF
import com.namasoft.infra.domainbase.datafields.LongTextDF
import com.namasoft.infra.domainbase.entity.base.EntityAction
import com.namasoft.infra.domainbase.entity.generic.GenericReference
import com.namasoft.infra.domainbase.persistence.repos.FilterOnDimensions
import com.namasoft.infra.domainbase.persistence.repos.ListPageMatchingParameters
import com.namasoft.infra.domainbase.persistence.repos.Persister
import com.namasoft.infra.domainbase.util.Result
import com.namasoft.modules.accounting.domain.details.RVPVDetailLine
import com.namasoft.modules.accounting.domain.entities.RVPVVoucher

import java.util.stream.Collectors

public class EACreateSerialForLineSubsidiary implements EntityAction<RVPVVoucher> {

    @Override
    Result doAction(RVPVVoucher doc, LongTextDF... parameters) {
        Result result = Result.createAccumulatingResult();
        Map<GenericReference, List<RVPVDetailLine>> map = CollectionsUtility.mapList(doc.fetchLines(), { l -> l.getSubsidiary() });
        for (Map.Entry<GenericReference, List<RVPVDetailLine>> entry : map.entrySet()) {
            if (ObjectChecker.isEmptyOrNull(entry.getKey()))
                continue;
            CriteriaBuilder.ExpressionBuilder builder = CriteriaBuilder.create()
                    .openBraket()
                    .field(NotGeneratedFields.valueDate).lessThan(doc.getValueDate())
                    .or().openBraket().field(NotGeneratedFields.valueDate).equal(doc.getValueDate()).and().field("creationDate").lessThan(doc.creationDate()).closeBraket()
                    .closeBraket().and()
                    .field(NotGeneratedFields.fiscalYear).equal(doc.getFiscalYear()).and().idNotEqual(doc.getId()).and().commitedBefore()
                    .and().field(AllModulesFieldIDs.PaymentVoucher_lines_subsidiary + ".id").equal(entry.getKey().getId());
            List<RVPVVoucher> docs = Persister.listPageMatching(doc.getClass(), ListPageMatchingParameters.create().pageData(PageData.One()).filterOnDimensions(FilterOnDimensions.No).
                    criteria(builder).orderBy(NotGeneratedFields.valueDate, NotGeneratedFields.creationDate).desc());
            RVPVVoucher lastDoc = CollectionsUtility.getLast(docs);
            List<RVPVDetailLine> lines = lastDoc == null ? new ArrayList<>() : lastDoc.fetchLines().stream().filter { l -> ObjectChecker.areEqual(l.getSubsidiary(), entry.getKey()) }.collect(Collectors.toList());
            RVPVDetailLine lastLine = CollectionsUtility.getLast(lines);
            int serial = lastLine == null ? 0 : lastLine.getN1().tryToInt()
            for (RVPVDetailLine line in entry.getValue()) {
                serial++;
                StringBuilder lineSerial = new StringBuilder();
                lineSerial.append(line.getSubsidiary().getCode()).append(" - ").append(doc.getFiscalYear().getCode()).append(" - ").append(serial);
                line.setNarration(LongTextDF.fromString(lineSerial.toString()));
                line.setN1(DecimalDF.fromInteger(serial.toInteger()));
            }
        }
        return result;
    }

    @Override
    String describe() {
        return "Receipt or payment voucher: numbers the lines of each subsidiary within the fiscal year. Every line that has a subsidiary gets the number " +
                "that follows the last line of the same subsidiary in the earlier committed vouchers of the same type and fiscal year. The number is written " +
                "to the line's N1, and the text <subsidiary code> - <fiscal year code> - <number> to its Narration. Takes no parameters."
    }

    @Override
    List<String> columnNames() {
        return Arrays.asList("Not used - leave empty")
    }
}