Choosing the Right Approach
Practice makes things perfect. The perfect way of writing code benefits both the Customer and Partner. There are hundreds of capabilities in TDL and a solution can be achieved with the different approaches. But, you should choose the right approach that gives a better experience in a longer period and even with the large volume of data. With this guide, you shall be able to understand the TDL capabilities and their elements with a comparison of a similar kind. This shall help you to decide when to use and what capability.
Set as with IF-THEN-ELSE condition Vs Set By condition
Set as with IF-THEN-ELSE
The attribute is utilised to set a value at the field. If the end-user wants to provide a conditional output developer can use the If-Then-Else construct. A nested If-Then-Else construct can be used to provide conditional output. However, the nested conditions can be provided only up to 13 conditions. In case more conditions are required, the last condition should be a formula. This formula can further contain more If-Then-Else constructs. When it is a nested condition, the readability of the condition is difficult.
Set By Condition
The attribute Set By Condition is similar to a conditional Set at Field level. If multiple Set By Condition is mentioned under a Field, then the last satisfied Set By Condition will be executed. There are no limitations on the number of usages of the attribute in the field. This attribute makes the code more readable. ‘Set By Condition’ is similar to Set As with condition in situations wherein both are specified, Set By Condition has precedence over Set As. Only when the condition specified at Set By Condition fails, Set As would be executed.
Option vs Switch
Option
When any definition needs to be executed based on condition sequentially, satisfying conditions, all the provided conditions are evaluated. The most appropriate condition is executed. In case of a higher number of options, all the options in the definition are evaluated and the most appropriate condition is executed. When the number of options is more and the conditions are complexed, the evaluation takes longer. Even though the most appropriate condition is met, the system continues to evaluate the conditions. The benefit of using an option is it allows you to execute multiple conditional definitions.
Switch
When any definition needs to be executed based on condition sequentially, satisfying conditions, however, the loop would break once the first condition is satisfied. Conditions can be grouped based on their criteria using labels.
Using the Switch is better as it has better readability and gives better performance. It breaks the loop once the satisfied condition is executed. Hence, this speeds up the performance. If multiple conditional definitions require to be executed, we need to provide different labels to each condition.
Scenario |
Requirement |
Bad Code |
Good Code |
Ideal Artifact |
Different Parts |
Depending on voucher Mode parts in voucher change |
[Part: EI Info] Option:EI InvInvoice : NOT @@AcctsInvoice Option:EI ACcInvoice : @@AcctsInvoice |
[Part: EI Info] Switch : EIInfo: EIInvInvoice : NOT @@AcctsInvoice Switch : EIInfo : EIAccInvoice : @@AcctsInvoice |
Switch |
Child Of vs Filter
Child Of and Filter attributes at Collection definition pulls out a set of data from a Superset. The functionality of both attributes seems similar. However, there is little difference between them.
Child Of |
Filter |
A subset of data is fetched from a particular object. This object should fall under the parent-child hierarchy like ledgers of a group, vouchers of a voucher type.
|
A subset of data is fetched from more than one single object. The filtering parameter may not be a parent object. When multiple filters are required to be applied like, vouchers of a particular date range, Stock items having non zero closing balance and GST applicable. |
The collection is gathered only for the parent object. Use cases like Trigger reports, Drilldown reports, where data gathered is for a preselected object. |
In the case of a filter, the collection is fully gathered, and then the ‘Filter’ is applied. |
‘Child of’ takes lesser memory and thus quicker reports. |
‘Filter’ is expensive in terms of memory and speed as it requires multiple iterating. |
Some quick examples to provide more details
Scenario |
Requirement |
Bad Code |
Good Code |
Ideal Artifact |
Ledgers of a group |
To filter a set of ledgers of a particular group |
[Collection: TSPLColl] Type : Ledger Filter : DebtorFilter |
[Collection : TSPLColl] Type : Ledger Child of : $$GroupSundryDebtors |
Child Of |
Vouchers of a Voucher Type |
To filter a set of transactions of a particular Type |
[Collection: TSPLColl] Type : Voucher Filter : SalesFilter |
[Collection: TSPLColl] Type : Vouchers : VoucherType Child Of : $$VchTypeSales |
|
Vouchers of a Ledger |
To filter a set of transactions of a particular ledger |
[Collection: TSPLColl] Type : Voucher Filter : LedgerFilter LedgerFilter: NOT $$IsEmpty:$LedgerEntries[1,@@LedEqual].LedgerName LedEqual: $LedgerName = “Sunny Enterprises”
|
[Collection: TSPLColl] Type : Vouchers : Ledger Child Of : “Sunny Enterprises” |
|
Vouchers of a VoucherType and a Ledger |
To filter a set of transactions of a particular Voucher type for a Ledger |
[Collection: TSPLColl] Type : Voucher Filter : LedgerFilter, VchTypeFilter LedgerFilter : NOT $$IsEmpty:$LedgerEntries[1,@@LedEqual].LedgerName LedEqual : $LedgerName = “Sunny Enterprises” VchTypeFilter: $$IsEqual:$VoucherTypeName:$$VchTypeSales |
[Collection: TSPLColl] Type : Vouchers : Ledger Child Of : “Sunny Enterprises” Filter : VchTypeFilter [System: Formulae] VchTypeFilter: $$IsEqual:$VoucherTypeName:$$VchTypeSales |
Combination of ‘Child of’ and ‘Filter’ |
Ledgers having zero Opening Balance |
To filter ledgers containing no opening balance |
———————————— |
[Collection: TSPLColl] Type : Ledger Filter : LedgerZero [System: Formulae] LedgerZero : $$IsEmpty:$OpeningBalance Or $OpeningBalance =< 0 |
Only Filters |
Stock items having non zero closing balance and GST applicable |
To filter stock items that has zero opening balance and GST is applicable |
———————————– |
[Collection: TSPLColl] Type : Stock Item Filter : NotZeroClBal, IsGSTAppl [System: Formulae] NotZeroClBal : NOT $$IsEmpty:$ClosingBalance IsGSTAppl : $GstApplicable |
Optimal Fetches
Each object in the Tally database has a huge set of hierarchies. Hence it is essential to fetch only the required set of data. When a collection is gathered, certain methods are fetched by default. Simultaneously, if required, the rest needs to be fetched explicitly using the ‘fetch’ attribute. The more the number of fetches, more the time consumed to gather the data. So it is a good practice to fetch only the required methods and refrain from using *. * <subcollection>. *, as this wild card; fetches all the methods. Using this method impacts the performance of data gathering and make the processing slow.
When to use .*?
Fetching all the methods with .* can be done only when
- The methods required is not known well ahead.
- The collection is used as a source collection for multiple summary collections where the methods required are not preset.
When not to use .*?
When the required methods are already known
Scenario |
Bad Code |
Good Code |
Fetching only required methods – in the main collection Fetching Narration and PartyName from voucher |
[Collection: TSPLVchColl] Type : Voucher Fetch : *.* |
[Collection: TSPLVchColl] Type : Voucher Fetch : Narration, PartyName |
Fetching required methods from a subcollection, in the main collection Fetching Amount and Rate from AllInventoryEntries, BatchName and Amount from BatchAllocations |
[Collection: TSPLVchColl] Type : Voucher Fetch : AllInventoryEntries.*, AllInventoryEntries.Batchallocations.* |
[Collection: TSPLVchColl] Type : Voucher Fetch : AllInventoryEntries.Amount, AllInventoryEntries.Rate Fetch : AllInventoryEntries.Batchallocations.BatchName, AllInventoryEntries.Batchallocations.Amount |
Using dotted notations
Every database object is connected to the interface object. An interface would be associated with a data object. When an Interface is in the context of a data object, any object method can be fetched. However, to fetch methods from sub-collection or another primary object’s subcollection, we would have to use platform functions to fetch values. The more functions involved in fetching the data, the higher the time consumption.
Scenario |
Bad code |
Good code |
Require Batch Name from Batch Allocations of the voucher |
;; This is a repeated line [Line : Expl InventoryLines] Field : Expl InventoryLines [Field : Expl InventoryLines] Set as : $$CollectionField:$StockItemName:$$Line:InventoryEntries [Line : Expl Batch Part] Field : Expl Batch Part [Field : Expl Batch Part] Set as : $$CollectionField:$MyBatchName:$$Line:InventoryEntries ;;Modifying Inventory Object [#Object : Inventory Entry] MyBatchName : $$FilterValue:$BatchName:BatchAllocations:First:IsStockName [System : Formula] IsStockName : #ExplInventoryLines = $StockItemName |
[Line : Expl InventoryLines] Field : Expl InventoryLines [Field : Expl InventoryLines] Set as : $InventoryEntries[$$Line].StockItemName [Line : Expl Batch Part] Field : Expl Batch Part [Field : Expl Batch Part] Set as : $InventoryEntries[1,#ExplInventoryLines = $StockItemName].BatchAllocations[$$Line].BatchName |
Batch posting
The database is locked for the time of updating data from external sources and unlocked on completion. Batch Posting action enables updating the database in a batch. In the absence of this action, each object will update the database, thus consuming high memory.
The bigger the batch size, the lesser the total number of batches, and the better the performance. The operation will be accomplished faster if the Batch Size specified is optimal. A very high Batch Size, beyond a particular point, may also deteriorate the performance. Hence, striking the right balance and specifying the optimal batch size is essential to achieve the best performance.
Scenario |
Bad code |
Good code |
Creating objects from an external collection Creating 180 ledgers from excel |
[Function: TSPLCreateLedgers] 001 : Walk Collection: TSPLExcelColl 002 : New Object: Ledger ….. ….. 033 : Save Target 034 : End Walk |
[Function: TSPLCreateLedgers] 000 : Start Batch Post : 50 001 : Walk Collection : TSPLExcelColl 002 : New Object: Ledger ….. ….. 033 : Save Target 034 : End Walk 035 : End Batch Post |
Data aggregation vs Totaling Functions
In every business report at the end, we need aggregated data and values. The aggregation can be achieved through functions using FilterAmountTotal, CollAmtTotal, CollNumTotal, FilterNumTotal, CollNumTotalEx, and an exhaustive list. Each time the functions are triggered/evaluated, the collection is constructed and scanned more than once if more than one aggregated value is required from the same collection. Thus, leading to a performance hit.
With the grouping and aggregation capabilities of the collection- Walk, By, and Aggr Compute, aggregated values can be obtained at a very high performance.
Benefits
- With the aggregation capabilities of collection, the required aggregated values are readily available in the collection, and collection needs to be constructed only once with required values.
- These capabilities also allow obtaining maximum and minimum values in a collection.
- With the help of CollectionFieldByKey or repeating the collection, these aggregated values can be accessed without reconstructing the collection.
Scenario |
Bad Code |
Good Code |
Stock Itemwise Total quantity and Amount for a period |
[Collection: VchColl] Type : Vouchers : VoucherType Child Of : $$VchTypeSales [Field: StkBilled] Set as : $$CollQtyTotal:VchColl:@StkTot Local Formula : StkTot : $$FilterQtyTotal:Allinventoryentries:StkEqual:$BilledQty [Field: StkActual] Set as : $$CollQtyTotal:VchColl:@StkTot Local Formula : StkTot: $$FilterQtyTotal:Allinventoryentries:StkEqual:$ActualQty [Field: StkAmount] Set as : $$CollAmtTotal:VchColl:@StkTot Local Formula : StkTot: $$FilterAmtTotal:AllInventoryEntries:StkEqual:$Amount [System : Formula] StkEqual: #StkName = $StockItemName |
[Collection: VchSalesColl] Type : Vouchers : VoucherType Child Of : $$VchTypeSales [Collection: VchCollInv] Source Collection : VchSalesColl Walk : All Inventory Entries By : StkName : $StockItemName AggrCompute : StkBilled : Sum : $BilledQty AggrCompute : StkActual : Sum : $ActualQty AggrCompute : StkAmt : Sum : $Amount |
Get maximum and minimum stock item purchase value |
[Collection: VchPurchaseColl] Type : Vouchers : VoucherType Child Of : $$VchTypePurchase [Function: GetMaxmInValue] 00: Walk : VchPurchaseColl 01: Walk : All Inventory Entries 02: …….Code to get maximum & minimum values 03: End Walk 04: End Walk |
[Collection : VchPurchaseColl] Type : Vouchers : VoucherType Child Of : $$VchTypePurchase [Collection : VchCollInv] Source Collection : VchPurchaseColl Walk : All Inventory Entries By : StkName : $StockItemName Aggr Compute : StkMax : Max : $Amount Aggr Compute : StkMin : Min : $Amount |
Use Keep Source
When a report is triggered, a collection is constructed in memory. This process takes both memory and time. When summary collections are referred, the source collection is gathered first, and then the summary collection is constructed. Suppose multiple summary collections are used having the same source collection. In that case, the same source collection is still gathered again and again, which becomes redundant. ‘Keep Source’ helps in storing source collection in cache memory and the source can be used more than once. Once the source collection is cached, the same cached data will be used when the collection is used as a source collection in the report. This attribute improves performance by eliminating the time taken in constructing the same source collection repeatedly. The Source collection can be cached at the current owner, owner’s parent and so on (with dots) or at the report level. The decision of where to cache the data depends on the elements in the report that uses the source data.
When to use
- When the same source collection is required for more than one summary collection used in the report.
- When the source collection data is used in multiple collections.
- Use Keep Source : (). (Cached at report level) when source collection is required to be used across parts. or when recursive explodes, use the same collection. (Group explodes to ledgers and groups, where groups again explode to ledgers and groups and so on)
When not to use
- When the source collection is used only once in the entire report
- When the source collection data is enormous, the system goes out of memory since holding those objects in memory in one shot is not possible.
- When source collection data is always low, and even if referred in multiple summary collections, there is minimal to no performance impact.
Scenario |
Bad Code |
Good Code |
Ledger entry and inventory entry details shown in 2 parts |
[Collection: TSPLVoucherSrc] Type : Voucher [Collection: TSPLLedgerEntry] Source Collection : TSPLVoucherSrc Walk : All Ledger Entries By : Ledger : $LedgerName … [Collection: TSPLLedgerEntry] Source Collection : TSPLVoucherSrc Walk : All Inventory Entries By : Stock : $StockItem … |
[Collection: TSPLVoucherSrc] Type : Voucher [Collection: TSPLLedgerEntry] Source Collection : TSPLVoucherSrc Walk : All Ledger Entries By : Ledger : $LedgerName Keep Source : (). … [Collection: TSPLLedgerEntry] Source Collection : TSPLVoucherSrc Walk : All Inventory Entries By : Stock : $StockItem Keep Source : (). … |
Use of Search Key
In a multi-dimension report, it is required to compare values of objects which may belong to the different or same set of the object of collections. Values can be obtained with the help of various Filter functions and attribute Filter at collections. However, this pulls down the performance of the report output. A better way to optimise the report is indexing. These objects can be indexed with the help of ‘Search Key’, which will associate the key specified with each object when the collection is gathered. With the help of $$CollectionFieldByKey, the methods of any specific object can be retrieved via the ‘Key’ specified. This ‘Key’ acts as a foreign key with the attribute ‘Search Key’ at Collection.
Scenario |
Bad Code |
Good Code |
Retrieve voucher amount for a Ledger for a cost center. Ledgers are rows and Cost centres are columns |
[Collection : LedCC] Use : Voucher Collection Walk : LedgerEntries, CategoryAllocations, CostCentreAllocations By : PartyLedgerName : $PartyLedgerName By : Cost Centre Name : $Name Aggr Compute : LedCCAmount : Sum : $Amount Set as : $$Filtervalue:$LedCCAmount:LedCC:1:MyFilter [System: Formula] MyFilter : (#LedName = $PartyLedgerName) And (#CCName = $Name)
|
[Collection : LedCC] Use : Voucher Collection Walk : LedgerEntries, CategoryAllocations, CostCentreAllocations By : PartyLedgerName : $PartyLedgerName By : Cost Centre Name: $Name Aggr Compute : LedCCAmount : Sum : $Amount Search Key : $PartyLedgerName + $CostCentreName
Set as : $$CollectionFieldByKey:$LedCCAmount:@MySearchKey:LedCC Local Formula : MySearchKey : #LedName + #CCName |
Use of WalkEx
In scenarios where we require a union of collections wherein source collection is the same, the gathering of source collection might happen once. However, still, the objects of source collection will be walked more than once. With walk ex, each object of source collection is walked only once, along the different walk paths. The attribute ‘Walk Ex’ results in performance improvements drastically.
Scenario |
Bad Code |
Good Code |
Union of Inventory and ledger entries of vouchers |
[Collection: TSPLResultColl] Collection : TSPlVchLed, TSPLVchInv [Collection : TSPlVchLed] Source Collection : VoucherColl Walk : AllLedgerEntries By : Particulars : $LedgerName Aggr Compute : Tot Amount : Sum: $Amount [Collection : TSPLVchInv] Source Collection : VoucherColl Walk : AllInventoryEntries By : Particulars : $StockItemName Aggr Compute : Tot Amount : Sum : $Amount |
[Collection: TSPLResultColl] Source Collection : VoucherColl WalkEx : TSPLVchInv, TSPlVchLed [Collection : TSPlVchLed] Walk : AllLedgerEntries By : Particulars : $LedgerName Aggr Compute : Tot Amount : Sum: $Amount [Collection : TSPLVchInv] Walk : AllInventoryEntries By : Particulars : $StockItemName Aggr Compute : Tot Amount : Sum: $Amount |
TDL Procedural Utilization
User-defined functions or TDL Procedural is a compelling capability of TDL. This artefact empowers TDL developers to execute actions in a defined process. In contrast, the actual flow of events that happens for that action was entirely platform controlled.
When and how to use TDL Procedural?
- When a specific interaction flow is required to be attained. E.g., based on particular user input action is performed. So the report is opened and based on inputs, further action is executed.
- When certain export and import operations are required
- Calling the TDL procedural should be done with CALL action when interaction flow is designed and not with $$.
- When certain computations are required, where it is complex to use system formulas.
- As part of interaction flow, specific evaluations can be done like setting a variable, which doesn’t lead to any unstable system state.
When not to use TDL procedural?
- When specific values are required from collection or object. Object referencing and Collection capabilities like aggregations, search key, extracting are powerful enough to obtain such value. DO NOT use functions in such cases, as function creates an overhead and slows down the system and impacts the performance.
- When computations are possible through system formulas, use system formulas and not functions.
Scenario |
Bad Code |
Good Code |
Getting the closing balance of a StockItem |
[Field: TSPLstkCls] Set as: $$GetStkClosingBal:#StockItem [Function:GetStkClosingBal] Parameter : pStK: String 00 : Walk : StkColl 02 : DO IF : ##pStK = $Name : Return : $ClosingBalance 03 : End Walk |
[Field: TSPLStkCls] Set As: $(StockItem,#StockItem).ClosingBalance |
Getting sales voucher values for a ledger for a stockitem |
[Field: LedStkAmount] Set as :$$GetStkLedAmount:#LedName:#StkName [Function : GetStkLedAmount] ….<Parameters pLed and pStk> <Variable vAmount> 00: Walk : SalesVchExtract 02: Walk : All Ledger Entries 03: If : ##pLed = $LedgerName 04: Walk : Inventory Allocations 05: If : ##pStk = $StockItemName 06: Set : vAmount : vAmount + $Amount 07: End If 08: End Walk ….. 100: Return : ##vAmount |
[Collction: SalesvchExtract] Source Collection : Salesvch Walk : AllLedgerEntries, InventoryAllocations By : Ledger : $LedgerName By : Stock: $StockItem Aggr Compute : LedStkAmount : Sum : $Amount Search Key : $Ledger + $Stock [Field: LedStkAmount] Set as : $$CollectionFieldByKey:$LedStkAmount:@MySearchKey:SalesVchExtract Local Formula : MySearchKey : #LedName + #StkName |
Getting Nett assesable value for a ledger based on tax type |
[Function: GetAssesablevalue] <parameter pledged> 001 : If : $$IsSysNameEqual:Vat:($TaxType:Ledger:##pledger) 002: Return : @ExciseMfgrItemRateVal + @ApportionValueForVat 003 : Else If : $$IsSysNameEqual:Excise:($TaxType:Ledger:##pledger) OR $$IsSysNameEqual:CENVAT:($TaxType:Ledger:##pledger) OR + 004 : Return : @ExciseMfgrItemRateVal + @ApportionValueForExcise 006 : Else 007 : Return : $$AsAmount:0 008: …… |
[System: Formula] NettAssVal:If $$IsSysNameEqual:Vat:($TaxType:Ledger:#SVLedger) + Then (@ExciseMfgrItemRateVal + @ApportionValueForVat) + ElseIf ($$IsSysNameEqual:Excise:($TaxType:Ledger:#SVLedger) OR $$IsSysNameEqual:CENVAT:($TaxType:Ledger:#SVLedger) OR + Else $$AsAmount:0 |
Events Utilisation
Events can be system events or report level events. Report events are primarily used to automate certain operations, like creating or updating objects. This capability will not hinder the normal execution of the default product. As per guidelines, any report customisations should be done by writing a new report.
System events like ‘On System Start’ and ‘On Company Load’ are more likely to hinder the product’s default interaction flow. Hence, it is better to refrain from using these events. For more details on Event Framework click here.
Action at Object definition
The action attribute at Object definition has simplified the association of a data object with UI element. Rather than creating various objects a single object with various actions can be defined and associated at the field. This enhances the way the code is written and make the life of a developer simpler. For more details on this topic click here.