Skip to content

Mark the From Document as Fulfilled ​

This Groovy script keeps a simple "done" flag on a from-document. Each time a document made from it is saved, the script checks whether all such documents together now cover every line of the from-document. If they do, it writes Fullfilled into the from-document's Description 1; if they do not, it empties that field.

Customer-specific example

This script was written for one customer's setup and is hard-wired in three ways. Adapt it before you use it elsewhere:

  • It writes into Description 1 of the from-document, and it empties that field whenever the from-document is not fully covered. Anything else kept in Description 1 is lost.
  • The word it writes is the literal text Fullfilled, spelt with a double l. Filters, reports and criteria must look for that exact spelling.
  • It only counts documents of the same type as the one being saved. Sales invoices made from an order count towards that order; stock issues made from the same order do not.
Class nameEAMarkFromDocFulfilled
ScriptEAMarkFromDocFulfilled.groovy
Runs onAny supply chain document made from another document. Two lines: PostCommit and PostDelete.
UsesNo parameters. Leave Parameter 1 empty.
Shipped asSource to paste. It is not built into the system.

When to use it ​

Sales orders are often invoiced in several parts. The order screen shows quantities, but a team that works from a list view or a report wants one column that simply says "this order is finished". This script gives them that column, kept up to date by the invoices themselves.

How it works ​

  1. It finds the from-document. If the document has no From Document, now or before this save, the script does nothing.
  2. It collects the documents made from it. These are the committed documents of the same type whose From Document is that from-document, plus the document being saved. On PostDelete the deleted document is left out.
  3. It adds up the quantities per source line. For each line of the from-document, it totals the base-unit quantity of every collected line that points to it.
  4. It writes the flag. When every from-document line is covered in full, Description 1 of the from-document becomes Fullfilled. When even one line is short, Description 1 is emptied.
  5. It also tidies a changed from-document. If the document used to point at a different from-document, that old one is recalculated as well, without this document.

The from-document is changed directly, in the same save. It is not re-saved through its own screen.

Setting it up ​

  1. Open Administration → Display Customization → Entity Flow and create a flow whose target type is the document made from the other one (for example Sales Invoice).
  2. In the Details grid, add two lines and paste the whole script into the Groovy Script column of each. Set the first line's Target Action to PostCommit and the second line's to PostDelete. Leave Class Name empty: it is filled in when you save.
  3. Leave all parameters empty. The only title shown after saving is Not used - leave empty on Parameter 1.
  4. Save the flow.

Example ​

A sales order has two lines, 2 units each, and its Description 1 holds an old reference. A first invoice is made from it for line 1 only. After saving, the order is not fully covered, so its Description 1 is emptied. A second invoice is then made for line 2. After that save, both lines are covered and the order's Description 1 reads Fullfilled.

The script ​

groovy
package com.namasoft.modules.supplychain.domain.utils.plugnplay.groovy

import com.namasoft.common.utilities.CollectionsUtility
import com.namasoft.common.utilities.ObjectChecker
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.datafields.TextDF
import com.namasoft.infra.domainbase.datafields.UniqueIDDF
import com.namasoft.infra.domainbase.entity.base.EntityAction
import com.namasoft.infra.domainbase.entity.generic.GenericReference
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.basic.domain.primitives.EntityTargetAction
import com.namasoft.modules.supplychain.domain.details.BasicSCDocumentLine
import com.namasoft.modules.supplychain.domain.entities.BasicSCDocument
import com.namasoft.modules.supplychain.domain.entities.dataholders.BasicSCDocumentDH

import java.util.stream.Collectors

class EAMarkFromDocFulfilled implements EntityAction<BasicSCDocument<BasicSCDocumentDH>> {
    @Override
    Result doAction(EntityTargetAction action, BasicSCDocument<BasicSCDocumentDH> object, LongTextDF... parameters) {
        Result result = Result.createAccumulatingResult();
        BasicSCDocument oldFromDoc = object.getOldData() == null ? null : GenericReference.toRealOrNull(object.getOldData().getFromDoc(), BasicSCDocument.class);
        BasicSCDocument fromDoc = GenericReference.toRealOrNull(object.getFromDoc(), BasicSCDocument.class);
        if (fromDoc == null && oldFromDoc == null)
            return result;
        applyOnFromDoc(fromDoc, object, action.isPostDelete() ? false : true);
        if (ObjectChecker.areNotEqual(fromDoc, oldFromDoc))
            applyOnFromDoc(oldFromDoc, object, false);
        return result;
    }

    void applyOnFromDoc(BasicSCDocument fromDoc, BasicSCDocument object, boolean apply) {
        if (fromDoc == null)
            return;
        List<BasicSCDocument> documents = Persister.listPageMatching(object.getEntityType(), ListPageMatchingParameters.create().criteria(
                CriteriaBuilder.create().field("fromDoc.id").equal(fromDoc.getId()).and().commitedBefore().and().field("id").notEqual(object.getId()).build()
        ));
        if (apply)
            documents.add(object);
        List<BasicSCDocumentLine> allLines = documents.stream().map({ it.getDetails() }).flatMap { it.stream() }.collect(Collectors.toList());
        Map<UniqueIDDF, List<BasicSCDocumentLine>> linesBySrcLineId = CollectionsUtility.mapList(allLines, { it.getSourceLineId() });
        boolean allLinesAreFullfilled = true;
        for (final BasicSCDocumentLine line in fromDoc.getDetails()) {
            List<BasicSCDocumentLine> lineLines = linesBySrcLineId.get(line.getId());
            if (DecimalDF.totalize(lineLines, { it.baseQtyValue() }).isLessThan(line.baseQtyValue())) {
                allLinesAreFullfilled = false;
                break;
            }
        }
        if (allLinesAreFullfilled) {
            fromDoc.allowSetting { fromDoc.setDescription1(TextDF.fromString("Fullfilled")) }
        } else {
            fromDoc.allowSetting { fromDoc.setDescription1(TextDF.fromString("")) }
        }
    }

    @Override
    String describe() {
        return "Marks this document's from-document as fulfilled. The script adds up, per from-document line, the base quantity of " +
                "the lines of every committed document of this same type that was made from it, this one included. When every " +
                "from-document line is fully covered, it writes Fullfilled into Description 1 of the from-document; otherwise it clears " +
                "Description 1. Add it twice, on PostCommit and on PostDelete. When the from-document is changed, the old one is " +
                "recalculated too. Parameter 1 is not used.";
    }

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