Putting it together
Time to use everything. The job: a despatch note arrives on a share. Its order number has been read by Captur. Gather every other document with that order number into one folder named after the customer.
This is a cut-down version of a script that runs for a real customer. It has the same shape.
Plan it first
Section titled “Plan it first”Before writing code, write the steps in plain words:
- Run when a document is indexed, or when someone corrects its fields.
- Stop unless the document is a despatch note with a usable order number.
- Work out the customer and the folder name.
- Find every document carrying that order number.
- Create the customer folder if it isn’t there.
- Copy each document in, unless it’s already there.
- Say what happened.
Each step is a few lines. Steps 1 and 2 are the guards from A safe event script in Reacting to events.
Step by step
Section titled “Step by step”Settings
Section titled “Settings”Everything that might differ between customers or shares goes in config parameters (see Settings that belong to the automation in Reacting to events), each with a default:
set errorblock to "onError"block onError printline "Error (line {{mash.error.line}}): {{mash.error.message}}"end
set refField to mash.config.get("REF_FIELD") ?? "Order No"set typeField to mash.config.get("DOCTYPE_FIELD") ?? "Document Type"set triggerType to mash.config.get("TRIGGER_DOC_TYPE") ?? "Despatch Note"set custField to mash.config.get("CUSTOMER_FIELD") ?? "Customer Name"set destShare to mash.config.get("DEST_SHARE")
if not destShare fail "DEST_SHARE is not set on this automation."enderrorblock = "onError"block onError() { printline("Error (line {{mash.error.line}}): {{mash.error.message}}")}
refField = mash.config.get("REF_FIELD") ?? "Order No"typeField = mash.config.get("DOCTYPE_FIELD") ?? "Document Type"triggerType = mash.config.get("TRIGGER_DOC_TYPE") ?? "Despatch Note"custField = mash.config.get("CUSTOMER_FIELD") ?? "Customer Name"destShare = mash.config.get("DEST_SHARE")
if (not destShare) { fail "DEST_SHARE is not set on this automation."}The error handler comes first, so any later failure prints a line you can find in the log. DEST_SHARE has no default because there’s no sensible one, so the script fails loudly rather than guessing.
The guards
Section titled “The guards”set doc to mash.event.file
if not doc returnendif doc.is_dir returnend
set docType to mash.str(doc.fields[typeField] ?? "").trim().toString()if mash.str(docType).lower() is not mash.str(triggerType).lower() returnend
set reference to mash.str(doc.fields[refField] ?? "").trim().toString()if mash.str(reference).length() is less than 3 printline "{{doc.name}} has no usable order number yet, nothing to gather." returnenddoc = mash.event.file
if (not doc) { return}if (doc.is_dir) { return}
docType = mash.str(doc.fields[typeField] ?? "").trim().toString()if (mash.str(docType).lower() != mash.str(triggerType).lower()) { return}
reference = mash.str(doc.fields[refField] ?? "").trim().toString()if (mash.str(reference).length() < 3) { printline("{{doc.name}} has no usable order number yet, nothing to gather.") return}Every return here is a quiet exit. Both triggers fire for every document on the share. Without the type check, each certificate would start its own gather.
The ?? "" matters. On the first Indexed event Captur hasn’t finished, so the fields are empty.
The folder name
Section titled “The folder name”The name is built from text a scanner read off a document, so treat it as untrustworthy. Slashes, colons and the like are not welcome in a folder name. Here is the logic on its own, which you can run:
block folderNameFor customer reference set name to mash.str(customer).trim().toString() if name is "" set name to "Unknown Customer" end set full to "{{name}} - {{reference}}" set full to mash.str(full).replaceMatches('#[\\/:*?"<>|]#', "-").toString() return mash.str(full).trim().toString()end
printline folderNameFor("Acme Ltd", "SO-20417")printline folderNameFor(" ", "SO-20418")printline folderNameFor("A/B: Traders", "SO-1")Acme Ltd - SO-20417Unknown Customer - SO-20418A-B- Traders - SO-1block folderNameFor(customer, reference) { name = mash.str(customer).trim().toString() if (name == "") { name = "Unknown Customer" } full = "{{name}} - {{reference}}" full = mash.str(full).replaceMatches('#[\\/:*?"<>|]#', "-").toString() return mash.str(full).trim().toString()}
printline(folderNameFor("Acme Ltd", "SO-20417"))printline(folderNameFor(" ", "SO-20418"))printline(folderNameFor("A/B: Traders", "SO-1"))Acme Ltd - SO-20417Unknown Customer - SO-20418A-B- Traders - SO-1Pulling the logic into a block means you can test it without a share, using the editor’s Run button, before the script ever sees a real document. The same trick works for the decision about which documents to skip:
set docs to [ {name: "despatch.pdf", type: "Despatch Note", ref: "SO-1"}, {name: "cert.pdf", type: "Certificate", ref: "SO-1"}, {name: "other.pdf", type: "Despatch Note", ref: ""}]
each docs as d set ref to mash.str(d["ref"] ?? "").trim().toString() if d["type"] is not "Despatch Note" next end if mash.str(ref).length() is less than 3 printline "{{d['name']}}: no usable reference yet" next end printline "{{d['name']}}: gather {{ref}}"enddespatch.pdf: gather SO-1other.pdf: no usable reference yetdocs = [ {name: "despatch.pdf", type: "Despatch Note", ref: "SO-1"}, {name: "cert.pdf", type: "Certificate", ref: "SO-1"}, {name: "other.pdf", type: "Despatch Note", ref: ""}]
each (docs) as d { ref = mash.str(d["ref"] ?? "").trim().toString() if (d["type"] != "Despatch Note") { next } if (mash.str(ref).length() < 3) { printline("{{d['name']}}: no usable reference yet") next } printline("{{d['name']}}: gather {{ref}}")}despatch.pdf: gather SO-1other.pdf: no usable reference yetFinding and filing
Section titled “Finding and filing”set filters to {}set filters[refField] to referenceset related to mash.fields.findFiles(filters)
if count(related) is 0 returnend
set parent to mash.files.get(destShare, "")set target to parent.make(folderNameFor(customer, reference), true)
set filed to 0set skipped to 0
each related as hit if mash.files.exists(destShare, target.path ~ "/" ~ hit["name"]) increment skipped by 1 else set source to mash.files.get(hit["share_id"], hit["path"]) source.duplicate(target) increment filed by 1 endend
printline "Order {{reference}}: {{filed}} filed, {{skipped}} already there."filters = {}filters[refField] = referencerelated = mash.fields.findFiles(filters)
if (count(related) == 0) { return}
parent = mash.files.get(destShare, "")target = parent.make(folderNameFor(customer, reference), true)
filed = 0skipped = 0
each (related) as hit { if (mash.files.exists(destShare, target.path ~ "/" ~ hit["name"])) { skipped += 1 } else { source = mash.files.get(hit["share_id"], hit["path"]) source.duplicate(target) filed += 1 }}
printline("Order {{reference}}: {{filed}} filed, {{skipped}} already there.")Three details here make the script safe to leave running:
make(name, true)reuses the folder if it exists, so every despatch note can run it.- It copies (
duplicate) rather than moves. Copying can be undone. Moving can’t. - It skips a document that’s already in the folder. The copies keep their order number, so they get indexed too and match the same search, and without this check the script would copy forever.
The whole script
Section titled “The whole script”Put the pieces together, with customer read the same way as reference and folderNameFor defined above the guards. This is the shape every event script in this style has: settings, guards, the work, a summary line.
set errorblock to "onError"block onError printline "Error (line {{mash.error.line}}): {{mash.error.message}}"end
block folderNameFor customer reference set name to mash.str(customer).trim().toString() if name is "" set name to "Unknown Customer" end set full to "{{name}} - {{reference}}" set full to mash.str(full).replaceMatches('#[\\/:*?"<>|]#', "-").toString() return mash.str(full).trim().toString()end
set refField to mash.config.get("REF_FIELD") ?? "Order No"set typeField to mash.config.get("DOCTYPE_FIELD") ?? "Document Type"set triggerType to mash.config.get("TRIGGER_DOC_TYPE") ?? "Despatch Note"set custField to mash.config.get("CUSTOMER_FIELD") ?? "Customer Name"set destShare to mash.config.get("DEST_SHARE")
if not destShare fail "DEST_SHARE is not set on this automation."end
set doc to mash.event.fileif not doc returnendif doc.is_dir returnend
set docType to mash.str(doc.fields[typeField] ?? "").trim().toString()if mash.str(docType).lower() is not mash.str(triggerType).lower() returnend
set reference to mash.str(doc.fields[refField] ?? "").trim().toString()if mash.str(reference).length() is less than 3 printline "{{doc.name}} has no usable order number yet, nothing to gather." returnend
set customer to mash.str(doc.fields[custField] ?? "").trim().toString()
set filters to {}set filters[refField] to referenceset related to mash.fields.findFiles(filters)
if count(related) is 0 returnend
set parent to mash.files.get(destShare, "")set target to parent.make(folderNameFor(customer, reference), true)
set filed to 0set skipped to 0
each related as hit if mash.files.exists(destShare, target.path ~ "/" ~ hit["name"]) increment skipped by 1 else set source to mash.files.get(hit["share_id"], hit["path"]) source.duplicate(target) increment filed by 1 endend
printline "Order {{reference}}: {{filed}} filed, {{skipped}} already there."errorblock = "onError"block onError() { printline("Error (line {{mash.error.line}}): {{mash.error.message}}")}
block folderNameFor(customer, reference) { name = mash.str(customer).trim().toString() if (name == "") { name = "Unknown Customer" } full = "{{name}} - {{reference}}" full = mash.str(full).replaceMatches('#[\\/:*?"<>|]#', "-").toString() return mash.str(full).trim().toString()}
refField = mash.config.get("REF_FIELD") ?? "Order No"typeField = mash.config.get("DOCTYPE_FIELD") ?? "Document Type"triggerType = mash.config.get("TRIGGER_DOC_TYPE") ?? "Despatch Note"custField = mash.config.get("CUSTOMER_FIELD") ?? "Customer Name"destShare = mash.config.get("DEST_SHARE")
if (not destShare) { fail "DEST_SHARE is not set on this automation."}
doc = mash.event.fileif (not doc) { return}if (doc.is_dir) { return}
docType = mash.str(doc.fields[typeField] ?? "").trim().toString()if (mash.str(docType).lower() != mash.str(triggerType).lower()) { return}
reference = mash.str(doc.fields[refField] ?? "").trim().toString()if (mash.str(reference).length() < 3) { printline("{{doc.name}} has no usable order number yet, nothing to gather.") return}
customer = mash.str(doc.fields[custField] ?? "").trim().toString()
filters = {}filters[refField] = referencerelated = mash.fields.findFiles(filters)
if (count(related) == 0) { return}
parent = mash.files.get(destShare, "")target = parent.make(folderNameFor(customer, reference), true)
filed = 0skipped = 0
each (related) as hit { if (mash.files.exists(destShare, target.path ~ "/" ~ hit["name"])) { skipped += 1 } else { source = mash.files.get(hit["share_id"], hit["path"]) source.duplicate(target) filed += 1 }}
printline("Order {{reference}}: {{filed}} filed, {{skipped}} already there.")To use it: save it, add it to Captur Extracted (for fields Captur fills) and Fields Changed (for fields a person corrects) under Settings > MaSH > Events, filter it to the share documents arrive on, choose its Configuration and set DEST_SHARE. Then drop a test despatch note onto the share and watch Logs.
Try it
Section titled “Try it”Change the script to file a certificate too, not only a despatch note. Hint: the check on docType is the only line that has to change, because the search by order number already finds everything. Think about what skipped protects you from before you do it.
Next, where to go from here.