Documents in Calculations — Complete Guide

This article is a complete guide to working with documents inside backend calculations on the TeamAssistant (TAS) platform. It covers reading and storing documents, generating DOCX files from templates, creating PDF and Excel files, working with ZIP archives, organizing documents in the DMS, and storing results in variables. Each chapter includes practical examples and real-world use cases from implementations.

This article is intended for TAS consultants working with calculations (backend JavaScript). The reference reflects TAS 5.7 and later — functions available only from newer versions are marked (e.g. vault.* from 5.17, ai.* from 5.15).

Key concepts

DMS (Document Management System) — the central document store of TAS. Every document is bound to a case (IPROC_ID), has a unique ID (DMSF_ID), a name, revisions, and optional attributes (tags, folders, logical type).

Attachment variable — a variable on the template through which the user uploads files on the task form. In a calculation it is accessed via vars['VARIABLE_NAME'] and its value is an array of files.

File identifier — most document functions accept a file in three interchangeable ways:

  • file name — e.g. 'contract.pdf' (searched on the current case, then in sub-processes and the main process),
  • file ID — the numeric DMSF_ID,
  • attachment variable — e.g. vars['ATTACHMENT'] (if it holds multiple files, the first one is used).

DMS functions verify that the file belongs to the current case (or its sub/parent process). Attempting to access a file from another case fails with the error Dms file does not belong to process.

Rules for document calculations

The same absolute rules apply to document calculations as to all TAS calculations:

  • No async / await / Promise / .then() — TAS performs its own transpilation of the code. All calls (including file operations) are written synchronously: const content = storage.getDmsContent('invoice.pdf', 'buffer');
  • Logging only via proc.info / proc.warn / proc.error — never console.log.
  • debug.error('message') — shows an error to the user and blocks task completion (e.g. "Cannot generate document, signature is missing").
  • Always use lib.iprocId() for the case ID.
  • Move repeated logic (document generators, connectors) into Scripts at the environment level — they can then be called from any template.

Reading documents and their content

Core functions for retrieving a document and its content from the DMS:

Function

Description

storage.getDmsEntity(id)

Returns the file entity (metadata — DMSF_ID, NAME, revisions…). Accepts a name, ID, or attachment variable.

storage.getDmsContent(id, encoding)

Returns the file content. Encoding: 'utf-8', 'base64', 'binary', or 'buffer' (returns a Buffer).

storage.getDmsAbsolutePath(id)

Absolute path of the file on the server filesystem — input for conversions and further processing.

lib.getFileContents(path, encoding)

Reads a file directly from a disk path (typically a template in the DMS store or a public file).

vars['ATTACHMENT'].getFileStream(encoding)

Stream of a file from an attachment variable.

lib.pdfToText(fileName)

Extracts the text content of a PDF document on the case — the basis for parsing and classification.

lib.getFileType(fileName)

Returns the file type based on its extension.

Example — read an attachment uploaded by the user and store the PDF text in a variable:

// Read the attachment uploaded by the user on the task form
const invoiceFile = storage.getDmsEntity(vars['INVOICE_ATTACHMENT']);

if (!invoiceFile) {
debug.error('No invoice was uploaded to the task.');
}

// Extract text content from the PDF
const pdfContent = lib.pdfToText(invoiceFile.NAME);
vars['INVOICE_TEXT'].setValue(JSON.stringify(pdfContent));

proc.info('Invoice text extracted', { file: invoiceFile.NAME, caseId: lib.iprocId() });

To quickly check whether the user uploaded anything at all, use lib.countFiles(vars['INVOICE_ATTACHMENT']) — it returns the number of files in an attachment variable.

Storing documents on a case

There are two main ways to create a new document on a case:

lib.storeAttachment(fileName, content, convertToBase64, lastRevisionId)

Stores content (a string or Buffer) as a new case document. Returns the DMSF_ID of the new file. The convertToBase64 parameter defaults to true — if you pass a Buffer (e.g. the output of docx.docxTemplater), pass false.

lib.storeAttachmentAsRevision(fileName, content, convertToBase64, revisionFileName)

Stores content as a new revision of an existing document — the version history stays together (the user sees it in the document window under the Revisions section).

storage.saveToDms(filePath, fileName)

Stores a file that already physically exists on disk (typically the output of a conversion or a temporary file) into the DMS. Returns the ID of the new file.

Example — generate a text protocol and store its ID in a variable:

const protocol = [
`Case protocol No. ${vars['CASE_NUMBER'].getValue()}`,
`Created by: ${lib.usersToDisplayName(lib.initiator())}`,
`Date: ${moment().format('DD.MM.YYYY HH:mm')}`
].join('\n');

const fileId = lib.storeAttachment('protocol.txt', protocol);

// Store the document ID into a variable for later use
vars['PROTOCOL_FILE_ID'].setValue(fileId);
proc.info('Protocol stored', { fileId });

Storing the DMSF_ID in a variable is the most reliable way to reference a document in later tasks of the process — the name may change, the ID will not.

Generating DOCX documents from a template

The most common use case: generating contracts, protocols, signature sheets, or orders from a pre-built Word template into which case data is filled automatically.

You can find a more detailed description of this functionality here: Creating a DOCX document

Two generation variants

Function

Placeholder syntax

When to use

docx.generate(dmsFileId, replaceMap, newFilename)

{placeholder}

Simple text replacement. The template is a DMS document; the output is stored directly on the case and its ID is returned.

docx.docxTemplater(config)

+++placeholder+++

Advanced templates — loops (FOR), images, tables, conditions, custom JS functions. Returns a Buffer that you store via lib.storeAttachment.

Variant 1 — docx.generate (simple replacement)

The template is a document uploaded on the case (or reachable from it). Placeholders in curly braces are used in the template, e.g. {companyName}.

const newFileId = docx.generate(
'template-contract.docx', // template in DMS (name, ID or attachment variable)
{
companyName: vars['COMPANY'].getTitle(),
contractDate: moment().format('DD.MM.YYYY'),
amount: vars['AMOUNT'].getValue()
},
'contract-generated.docx' // new file name
);

if (!newFileId) {
debug.error('Contract generation failed.');
}
vars['CONTRACT_FILE_ID'].setValue(newFileId);

Variant 2 — docx.docxTemplater (advanced templates)

The Word template uses triple-plus tags +++. The variable name defined in the calculation data is placed between the tags.

For example, +++companyName+++ inserts the value of the companyName key from the data object.

Steps to prepare and deploy a template:

  1. Create a Word document with fixed content (header, footer, structure) and +++name+++ tags where data should go.
  2. Upload the document to TAS in the Documents section (Upload file).
  3. Find the uploaded document using the filters under Documents > All.
  4. Open the browser console (F12), the Network tab, and filter for the uploaded document.
  5. Copy the response value after "DMS:" — e.g. _7d5/template-podaciArch.docx.7d5fd45a805c10856ed4b07b9ce9048b.1.1655895144727
  6. Use the path in the calculation to load the template.
If you upload a new version of the template, the path changes — the procedure for finding the path must be repeated and the calculation updated.

Complete generation calculation:

// 1) Collect data from the case
const companyName = vars['companyName'].getValue();
const date = moment(vars['today'].getValue()).format('D. M. YYYY');
const items = vars['itemsDR'].getJSON(); // Dynamic Rows
const typeOfCost = dt.from('001-typeOfCost').get(); // Dynamic Table rows
const internalCaseNr = vars['internalCaseNr'].getValue();

// 2) Load the Word template from the DMS storage path
const template = lib.getFileContents(
'/app/tas/dms/_7d5/template-podaciArch.docx.7d5fd45a805c10856ed4b07b9ce9048b.1.1655895144727',
'binary'
);

// 3) Generate the document
const generated = docx.docxTemplater({
template,
data: { companyName, date, items, typeOfCost, internalCaseNr }
});

// 4) Store the generated file on the case
const fileId = lib.storeAttachment('protocol_generated.docx', generated, false);
vars['GENERATED_DOC_ID'].setValue(fileId);

Bulleted lists in a template

In the Word template:

+++FOR myBullet IN myBullets+++
• +++= $myBullet+++
+++END-FOR myBullet+++

In the calculation, just pass the array into data: const myBullets = vars['list'].getValue();

Tables from a dynamic table

In a Word table, iterate over the rows — the first cell of the row holds the opening tag, the last row the closing one:

+++FOR row IN typeOfCost+++
+++= $row.DTV_INDEX+++ +++= $row.COL_1+++ +++= $row.COL_2+++
+++END-FOR row+++

In the calculation: const typeOfCost = dt.from('001-typeOfCost').get();

Tables from dynamic rows (DR)

A DR has a column structure (an object of arrays), so iterate over the index:

+++FOR index IN Array.from(Array(items.itemName.length).keys())+++
+++= items.itemName[$index]+++ +++= items.unitPrice[$index]+++ USD
+++END-FOR index+++

In the calculation: const items = vars['itemsDR'].getJSON();

Images in a template (signatures, stamps)

In the Word template: +++IMAGE workerSignature()+++

const signatureBase64 = signatureFileName
? lib.getFileContents(`${signaturesPath}/${signatureFileName}`, 'base64')
: '';

const generated = docx.docxTemplater({
template,
data: docxData,
processLineBreaksAsNewText: true,
additionalJsContext: {
workerSignature: func => ({
width: 4,
height: 2,
data: signatureBase64,
extension: '.png'
})
}
});

Generating PDF — prints and conversions

PDF from a process print template

If the template has a defined Print (the Print section of the template — HTML/CSS or React), it can be rendered to PDF from a calculation and stored as a case document:

lib.printToFantomPdf(printName, fileName, forceNewPrint)

Generates a PDF from the print template of the given name and stores it on the case as fileName. The parameter forceNewPrint = true forces generation via Puppeteer (New prints) — recommended for modern React prints.

lib.printToFantomPdf('Order - print', `order_${vars['ORDER_NUMBER'].getValue()}.pdf`, true);

lib.savePrintForm(id, docType, fileName)

Saves a case print report. The parameter id is the PRNT_ID of the print (-1 = default print), docType is 'pdf', 'doc', 'html', or 'printer'.

Converting documents to PDF (LibreOffice)

lib.libreConvertDmsFile(fileVariable, outFormat, filter)

Converts files from an attachment variable to another format (default 'pdf') and stores them on the case. Typical use: the user uploads a DOCX and the process needs a PDF to sign.

lib.libreConvertDmsFile(vars['CONTRACT_DOCX'], 'pdf');

lib.libreConvert(srcFile, outFile, filter) — the same conversion over disk paths.

Merging multiple PDFs into one

lib.dmsMergePdfs(inFileNames, outFileName, isOrdered)

Merges the case's PDF documents into a single resulting file. With isOrdered = true, the page order follows the order of the names in the inFileNames array.

lib.dmsMergePdfs(
['contract.pdf', 'attachment_1.pdf', 'signature_sheet.pdf'],
'contract_complete.pdf',
true
);
Only PDF files that exist on the case in their current (non-deleted) version are merged.

Stamps, signatures, and QR codes in PDF

  • lib.modifyPdfAddImage(...) / lib.modifyPdfAddImageBase64(...) — inserts an image (stamp, signature) at a chosen position in a PDF.
  • lib.createQrCodeBase64(text, options) — generates a QR code (e.g. a QR payment) as base64 — can be inserted into a PDF or DOCX template via +++IMAGE+++.
  • lib.createBarcodeBase64(text, options) — the same for a barcode.

Working with Excel files

The excel API wraps the SheetJS (xlsx) library. It allows both reading and creating Excel files.

Reading an Excel uploaded by the user

// Load the attachment content as buffer and parse the workbook
const content = storage.getDmsContent(vars['IMPORT_XLSX'], 'buffer');
const workbook = excel.read(content, { type: 'buffer' });

// Convert the first sheet to JSON rows
const sheetName = workbook.SheetNames[0];
const rows = excel.utils().sheet_to_json(workbook.Sheets[sheetName]);

proc.info('Imported rows', { count: rows.length });

Creating an Excel and storing it on the case

// Build report data (e.g. from a Dynamic Table)
const data = dt.from('001-orders', ['COL_1', 'COL_2', 'COL_3'])
.whereCol('COL_3', 'APPROVED')
.get()
.map(row => ({
'Order': row['COL_1'],
'Supplier': row['COL_2'],
'Status': row['COL_3']
}));

// Create workbook + worksheet and store to DMS
const wb = excel.workbook();
const ws = excel.jsonToSheet(data, { skipHeader: false });
excel.appendWorksheet(wb, ws, 'Report');
excel.writeToDms(wb, 'report_orders.xlsx', { bookType: 'xlsx' });

Individual cells can be edited via excel.setCell(worksheet, 'A1', { t: 's', v: 'Title' }) — the type t is 's' (string), 'n' (number), etc.

Working with CSV

  • storage.getCsvFile(filename, options) — loads a stored CSV file from the environment's CSV store; options: { firstLineHeaders, returnAsObject, delimiter, quotes }. Returns an array of objects.
  • lib.csvToArray(file) / lib.csvToJson(data, delimiter, firstLineColNames, parameters) — parsing CSV content.
  • lib.jsonToCsv(data, mapping) — converts data to a CSV string, which can be stored via lib.storeAttachment('export.csv', csvString).
  • lib.updateDTFromCsv(dynTableName, csv, firstLineHeaders, delimiter, operation, fileName) — fills a dynamic table with CSV content (operation replace / merge / append).

Example — import a price list from an uploaded CSV into a dynamic table:

const csvContent = storage.getDmsContent(vars['PRICE_CSV'], 'utf-8');
lib.updateDTFromCsv('001-pricelist', csvContent, true, ';', 'replace', 'pricelist.csv');
proc.info('Pricelist updated from CSV');

Working with ZIP archives

The zip API is used to pack case documents into an archive and to unpack received archives.

Creating a ZIP from case documents

// Collect IDs of all PDF documents on the case
const fileIds = lib.getDMSFileIds('*.pdf', '|').split('|').filter(Boolean);

zip.start();
zip.addDmsFiles(fileIds);
zip.storeZip(); // saves to temp folder

// Store the zip on the case as a document
const zipId = storage.saveToDms(zip.getTempFilePath(), 'case_documentation.zip');
zip.deleteTempZip();

vars['ARCHIVE_FILE_ID'].setValue(zipId);

Extracting a ZIP archive

  • zip.unzipDmsFile(id) — extracts a ZIP from the DMS and attaches the individual files to the case as documents.
  • zip.unzipAndExport(id, exportPath, flattening) — extracts a ZIP from the DMS and exports the content to a given disk path.
  • zip.gzip(data) / zip.gunzip(data) — compresses and decompresses data (e.g. for integrations).

Organizing documents — folders, attributes, revisions

DMS folders

  • storage.createDmsFolder(folderName, parentFolderId) — creates a folder (if it does not exist) and returns it.
  • storage.getDmsFolder(folderName, parentFolderId) — finds a folder; result.FOLDER_ID can be used as the parent.
  • storage.setDmsFileFolder(id, folderName, parentId) — places a document into a folder (the folder is created automatically if it does not exist).
const contractId = docx.generate('template.docx', data, 'contract.docx');
storage.setDmsFileFolder(contractId, 'Contracts');

DMS attributes (tags) and logical type

  • storage.setDmsTagValue(tagId, value, files) — sets the value of a DMS attribute (text / number / date / list of values) for the given documents. Without the files parameter, it is set on all case documents.
  • lib.setDocumentLogicalType(fileName, logicTypeKey) — sets the logical type of a document (the code list is managed by $Administrator under Administration > DMS attributes).

Revisions, existence, deletion, and export

  • lib.storeAttachmentAsRevision(...) — a new version of an existing document (see the Storing chapter).
  • storage.getDmsEntityByName(name, searchRevisions) — with searchRevisions = true it also searches older revisions.
  • storage.dmsFileExists(id) — checks the physical existence of the file.
  • storage.dmsDeletePermanently(id) — permanently deletes a document including all revisions (irreversible!).
  • storage.exportDmsFile(id, destination) — copies a document to a disk path (e.g. to hand it over to an external system through a shared directory).
  • lib.exportAttachmentToTMP(identifier, exportDocName, encoding) — export to the TMP folder; clean up after processing via lib.cleanupTMP(tmpFolder).
  • storage.getAttachmentsSize(ids) — total size of files in bytes (e.g. checking an email attachment limit).
storage.dmsDeletePermanently deletes a document irreversibly, including all revisions. Use it only for temporary / generated files, never for documents uploaded by users unless it is an explicit process requirement.

Searching for documents on a case

  • lib.getDMSFiles(nameFilter, glue, attrNames) — finds case attachments by name filter ('*' = all, otherwise a case-sensitive comparison) and returns the selected attributes joined by the glue character.
  • lib.getDMSFileNames(nameFilter, glue) — returns the names of the found files.
  • lib.getDMSFileIds(nameFilter, glue) — returns the IDs of the found files.
// All PDF files on the case as an array of IDs
const pdfIds = lib.getDMSFileIds('*.pdf', ';').split(';').filter(Boolean);

// Total size check before sending by email (limit 10 MB)
const totalSize = storage.getAttachmentsSize(pdfIds);
if (totalSize > 10 * 1024 * 1024) {
debug.error('Documents exceed the 10 MB limit for sending by email.');
}

Sending documents

  • lib.addMailNotifsAttachment(dmsfName) — attaches a case document to the outgoing email notification of a task. Called in the calculation of the task whose notification should contain the document.
  • lib.copySharedFileLink(iprocId, varName) — copies a link to a shared file between cases.
  • lib.addPublicFileToAttachment(publicFileName, dmsAttachmentName) — attaches a public environment file (e.g. terms and conditions) as a case document.
  • Sending a file to an external system via HTTP — the axios client from a connector in Scripts can also send a file (see the axios documentation, methods for files / Buffer.from).

AI document extraction

ai.processDocument(fileName, schema)

From TAS 5.15 (with AI integration enabled), it sends a case document to the AI, extracts structured data from it according to a schema, and fills case variables. Typical use: automatic extraction of a received invoice (supplier, amount, due date, line items) after it is uploaded or received by email.

Availability of the function depends on the TAS version and whether AI is activated on the client installation — verify with the environment administrator before use.

Real-world use cases — complete examples

Use case 1 — Generating a contract and converting it to PDF for signing

Scenario: After an order is approved, a contract (DOCX) is generated from a template, converted to PDF, filed into a folder, and its ID is stored in a variable for the signing task.

// 1) Generate the contract from a Word template stored on the case
const contractId = docx.generate('template-contract.docx', {
supplierName: vars['SUPPLIER'].getTitle(),
orderNumber: vars['ORDER_NUMBER'].getValue(),
totalAmount: vars['TOTAL_AMOUNT'].getValue(),
signDate: moment().format('DD.MM.YYYY')
}, `contract_${vars['ORDER_NUMBER'].getValue()}.docx`);

if (!contractId) {
debug.error('Contract generation failed — check the template.');
}

// 2) Convert the generated DOCX to PDF (stored back on the case)
vars['CONTRACT_DOCX_ID'].setValue(contractId);
lib.libreConvertDmsFile(vars['CONTRACT_DOCX_ID'], 'pdf');

// 3) Organize into a folder and log
storage.setDmsFileFolder(contractId, 'Contracts');
proc.info('Contract generated and converted', { contractId, caseId: lib.iprocId() });

Use case 2 — Monthly Excel report from a dynamic table (cron)

Scenario: A scheduled calculation compiles an overview of approved orders into an Excel file each month and stores it on a reporting case.

const monthLabel = moment().subtract(1, 'month').format('YYYY-MM');

const rows = dt.from('001-orders', ['COL_1', 'COL_2', 'COL_4', 'COL_5'])
.whereCol('COL_5', 'APPROVED')
.orderBy('COL_1', 'asc')
.get()
.map(r => ({
'Order number': r['COL_1'],
'Supplier': r['COL_2'],
'Amount': r['COL_4']
}));

const wb = excel.workbook();
excel.appendWorksheet(wb, excel.jsonToSheet(rows, { skipHeader: false }), monthLabel);
excel.writeToDms(wb, `report_${monthLabel}.xlsx`, { bookType: 'xlsx' });

proc.info('Monthly report generated', { month: monthLabel, rows: rows.length });

Use case 3 — Compiling documentation: merge PDF + ZIP archive

Scenario: At the end of the process, all PDF documents are merged into a single "complete" file and, at the same time, all documents are packed into a ZIP for archiving.

// 1) Merge key PDFs into one document (order preserved)
lib.dmsMergePdfs(
['contract.pdf', 'attachment_1.pdf', 'acceptance_protocol.pdf'],
'complete_documentation.pdf',
true
);

// 2) Zip all case documents
const allIds = lib.getDMSFileIds('*', '|').split('|').filter(Boolean);
zip.start();
zip.addDmsFiles(allIds);
zip.storeZip();
const zipId = storage.saveToDms(zip.getTempFilePath(), `archive_${lib.iprocId()}.zip`);
zip.deleteTempZip();

storage.setDmsFileFolder(zipId, 'Archive');
vars['ARCHIVE_ID'].setValue(zipId);

Use case 4 — Importing data from an uploaded Excel into dynamic rows

Scenario: The user uploads an Excel with order line items; the calculation reads them and fills a DR variable on the form.

const content = storage.getDmsContent(vars['ITEMS_XLSX'], 'buffer');
const workbook = excel.read(content, { type: 'buffer' });
const rows = excel.utils().sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);

// Derive the DR column structure from the existing variable (never hardcode)
const existingDR = vars['ORDER_ITEMS'].getJSON();
const newDR = Object.fromEntries(Object.keys(existingDR).map(key => [key, []]));

rows.forEach(row => {
newDR.itemName.push(row['Name']);
newDR.quantity.push(row['Quantity']);
newDR.unitPrice.push(row['Unit price']);
});

vars['ORDER_ITEMS'].setValue(JSON.stringify(newDR));
proc.info('Items imported from Excel', { count: rows.length });

Function reference summary

Area

Functions

Reading

storage.getDmsEntity, storage.getDmsContent, storage.getDmsAbsolutePath, lib.getFileContents, lib.pdfToText, vars['X'].getFileStream

Storing

lib.storeAttachment, lib.storeAttachmentAsRevision, storage.saveToDms, excel.writeToDms

Generating DOCX

docx.generate, docx.docxTemplater

PDF

lib.printToFantomPdf, lib.savePrintForm, lib.libreConvertDmsFile, lib.libreConvert, lib.dmsMergePdfs, lib.modifyPdfAddImage(Base64), lib.createQrCodeBase64, lib.createBarcodeBase64

Excel / CSV

excel.read, excel.workbook, excel.jsonToSheet, excel.appendWorksheet, excel.setCell, storage.getCsvFile, lib.csvToJson, lib.jsonToCsv, lib.updateDTFromCsv

ZIP

zip.start, zip.addDmsFile(s), zip.storeZip, zip.getTempFilePath, zip.deleteTempZip, zip.unzipDmsFile, zip.unzipAndExport, zip.gzip/gunzip

Organizing

storage.createDmsFolder, storage.setDmsFileFolder, storage.setDmsTagValue, lib.setDocumentLogicalType

Searching / checking

lib.getDMSFiles/Names/Ids, lib.countFiles, storage.dmsFileExists, storage.getAttachmentsSize, storage.getDmsEntityByName

Export / deletion

storage.exportDmsFile, lib.exportAttachment(ToTMP), lib.cleanupTMP, storage.dmsDeletePermanently

Sending / AI

lib.addMailNotifsAttachment, lib.addPublicFileToAttachment, lib.copySharedFileLink, ai.processDocument (5.15+)

The exact signatures of all methods can be found in the JSDoc documentation of the individual APIs (StorageApi, DocxApi, ExcelApi, ZipApi, EvalMathApi) on the client installation.

Frantisek Brych Updated by Frantisek Brych

DOCX document creation

Contact

Syca (opens in a new tab)

Powered by HelpDocs (opens in a new tab)