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 name | EACreateSerialForLineSubsidiary |
| Script | EACreateSerialForLineSubsidiary.groovy |
| Runs on | Receipt Voucher or Payment Voucher, on Validate On Save |
| Uses | No parameters. Leave Parameter 1 empty. |
| Shipped as | Source 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
- It groups the lines by subsidiary. Lines with no subsidiary are left alone.
- 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.
- 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
- Open Administration → Display Customization → Entity Flow and create a new flow with Target Type set to Payment Voucher (or Receipt Voucher).
- 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.
- Save the flow. Class Name shows the script's class name, and the description appears in Description.
| Parameter | Title shown after saving | What to enter |
|---|---|---|
| Parameter 1 | Not used - leave empty | Nothing. |
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.
| Line | Subsidiary | N1 | Narration after saving |
|---|---|---|---|
| 1 | S0001 | 1 | S0001 - 2026 - 1 |
| 2 | (none) | unchanged | |
| 3 | S0001 | 2 | S0001 - 2026 - 2 |
The next payment voucher to S0001 continues the count: its line gets N1 3 and the narration S0001 - 2026 - 3.
The script
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")
}
}Related
- EAGroovyAction: how Groovy scripts are attached to entity flows and task schedules.
- Receipt & Payment Vouchers