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.
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 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— neverconsole.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 |
| Returns the file entity (metadata — |
| Returns the file content. Encoding: |
| Absolute path of the file on the server filesystem — input for conversions and further processing. |
| Reads a file directly from a disk path (typically a template in the DMS store or a public file). |
| Stream of a file from an attachment variable. |
| Extracts the text content of a PDF document on the case — the basis for parsing and classification. |
| 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() });
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 });
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 |
|
| Simple text replacement. The template is a DMS document; the output is stored directly on the case and its ID is returned. |
|
| Advanced templates — loops (FOR), images, tables, conditions, custom JS functions. Returns a Buffer that you store via |
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.
+++companyName+++ inserts the value of the companyName key from the data object.Steps to prepare and deploy a template:
- Create a Word document with fixed content (header, footer, structure) and
+++name+++tags where data should go. - Upload the document to TAS in the Documents section (Upload file).
- Find the uploaded document using the filters under Documents > All.
- Open the browser console (F12), the Network tab, and filter for the uploaded document.
- Copy the response value after
"DMS:"— e.g._7d5/template-podaciArch.docx.7d5fd45a805c10856ed4b07b9ce9048b.1.1655895144727 - Use the path in the calculation to load the template.
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
);
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' });
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 vialib.storeAttachment('export.csv', csvString).lib.updateDTFromCsv(dynTableName, csv, firstLineHeaders, delimiter, operation, fileName)— fills a dynamic table with CSV content (operationreplace/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_IDcan 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 thefilesparameter, it is set on all case documents.lib.setDocumentLogicalType(fileName, logicTypeKey)— sets the logical type of a document (the code list is managed by$Administratorunder 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)— withsearchRevisions = trueit 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 vialib.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 thegluecharacter.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.
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 |
|
Storing |
|
Generating DOCX |
|
| |
Excel / CSV |
|
ZIP |
|
Organizing |
|
Searching / checking |
|
Export / deletion |
|
Sending / AI |
|
StorageApi, DocxApi, ExcelApi, ZipApi, EvalMathApi) on the client installation.
Updated
by Frantisek Brych