Extending Tax Reports With TRF (NetSuite Partners)
This topic is relevant to NetSuite Partners only.
Report extensions enable NetSuite partners to implement sections of tax reports by writing code that defines how these sections are to be sourced and represented in the tax report. This feature is useful to quickly develop a workaround when an issue is found in tax reports.
An extension is logic that is going to be executed at a specific stage of report generation:
-
Builder stage: report data is being retrieved
-
Preprocessor stage: when sourcing logic is applied to each row returned by the builder
-
Postprocessor stage: when sourcing logic is applied to retrieved data as a whole
-
Export processor stage: when the final report is built, or a document is generated
Extension logic is executed only after the executing of the standard report generation logic as developed by NetSuite. At each stage, the standard NetSuite logic is executed first, followed by extension logic, if applicable. The extension will receive as input the output of the standard logic execution, plus some context data that is likely to be useful to compute the final result.
Known Limitations
-
A single report can have as many extension files as needed, but the order of their execution order is not specified.
-
Current implementation only supports data sources of type "query" and query builders of type "suiteql".
-
Template extensibility is not supported. If a report template or a HTML report preview needs to be extended, the recommended course of action for NetSuite's localization teams is the following:
-
Coordinate with the partner on what features should be added.
-
Prepare a default, empty implementation for the new box or section (report previews must be updated so that boxes are not shown unless they are actually implemented). This type of implementation may also include declaring detail views for new boxes, with an empty implementation for builders and processors.
-
Distribute a new version of the SuiteApp, so that the partner can extend the default implementation, providing values for the new boxes.
-
-
Report configuration pages extensibility is not supported. As a workaround, define configuration properties at subsidiary level, or in custom records.
Writing Tax Report Extensions
When developing an extension, you can choose to customize one or more stages, depending on your business needs. Typically, when adding a new box to a report for example, you will need to customize a builder to retrieve values from the database. You may have to customize pre-processors and/or post-processors to adjust that data or to update total boxes, and you will customize the export processor that eventually generates the report.
Report extensions are developed through plugin implementations, as the following code snippet shows.
In the following example, extensions were applied to all four stages. If any of these stages doesn't need to be customized, its implementation can return an empty collection. The following parameters are passed to the extension in each stage:
-
customizeBuilders: the query to be executed
-
customizePreProcessors: the object representing the result of the query, and the object produced by the standard preprocessor
-
customizePostProcessors: the job definition (including the box ID, for detail views), the TRF data manager, which gives access to preprocessor results, and the object produced by the standard postprocessor
-
customizeExportProcessors: the object produced in the postprocessor stage and the object produced by the standard export processor
Each extension must be bound to a specific script, identified by a script path. After that specific script is executed, the extension is applied.
As you can see from the example, each stage can have multiple extensions. For example, the build stage applies to the main report generation, but it's also required when generating detail views for boxes.
/**
* @NApiVersion 2.1
* @NScriptType plugintypeimpl
*/
define(['N/log'], function (logger) {
return {
// Builder stage
customizeBuilders: () => ([
{
script: 'SuiteApps/com.netsuite.spainlocalization/src/builders/modelos/Modelo303SummaryBuilder',
implementation: (query) => {
logger.debug('Builders', 'Executing builder')
// Add select list items
const index = query.indexOf('FROM');
query = query.substring(0, index) + ', SUM(tx.taxamount) allTaxAmounts, MAX(s.custrecord_sample_property_m303) customProperty ' + query.substring(index);
// Add a table.
const matches = [...query.matchAll(/WHERE/g)];
query = query.substring(0, matches[1].index) + 'JOIN subsidiary s ON s.id = tx.subsidiary ' + query.substring(matches[1].index);
return query;
}
},
{
script: 'SuiteApps/com.netsuite.spainlocalization/src/builders/DomesticSaleDetailsBuilder',
implementation: (query) => {
logger.debug('Builders', 'Executing detail builder')
return query;
}
}
]),
// Preprocessor stage
customizePreProcessors: () => ([{
script: 'SuiteApps/com.netsuite.spainlocalization/src/processors/modelos/Modelo303SummaryPreProcessor',
implementation: (inputData, processorResult) => {
logger.debug('Preprocessors', 'Executing preprocessor')
processorResult.alltaxamounts += 10;
processorResult.customproperty = processorResult.customproperty == 'T' ? true : false;
return processorResult;
}
}]),
// Postprocessor stage
customizePostProcessors: () => ([
{
script: 'SuiteApps/com.netsuite.spainlocalization/src/processors/modelos/Modelo303SummaryPostProcessor',
implementation: (definition, dataManager, processorResult) => {
logger.debug('Postprocessors', 'Executing postprocessor')
processorResult.alltaxamounts += 10;
return processorResult;
}
},
{
script: 'SuiteApps/com.netsuite.spainlocalization/src/processors/modelos/Modelo303DetailsPostProcessor',
implementation: (definition, dataManager, processorResult) => {
logger.emergency('Postprocessors', 'Executing detail postprocessor for box ' + definition.id)
if (definition.id == 'box1234') {
return [];
}
return processorResult;
}
}]),
// Export processor stage
customizeExportProcessors: () => ([{
script: 'SuiteApps/com.netsuite.spainlocalization/src/processors/modelos/Modelo303TxtProcessor.js',
implementation: (inputData, processorResult) => {
logger.debug('Export processors', 'Executing export processor')
processorResult.modelo303 += 'Custom data: ' + inputData.alltaxamounts;
return processorResult;
}
}])
};
});
When is an Extension Applied
An extension will only be applied if the corresponding plugin implementation is not marked as Inactive and the corresponding report is marked as customizable.
To mark a report as customizable, add the isCustomizable property, set to true, to your report in str_localized_reports_list.json, as shown in the example below. If the property is not given, the report is considered as not customizable.
{
"name": "VAT Modelo 303",
"country": "ES",
"report": "VAT",
...
"isCustomizable": true,
"translations": {
...
},
"details": [
...
]
}
Each generated report contains a property that shows whether it was generated with standard NetSuite logic or extended. This property is called custrecord_str_job_customized and is located in custom record customrecord_str_job. If the value of this property is null, the report is considered as not customized. Every generated extended report also contains the string (extended) in the report title.
Related Topics
- Tax Reporting Framework
- Roles and Permissions in Tax Reporting Framework
- Country Tax Reports in Tax Reporting Framework
- Setting Up Country Tax Reporting Preferences
- Generating Localized Country Tax Reports
- Viewing a Generated Country Tax Report
- Making Adjustments on a Country Tax Report
- Exporting a Country Tax Report
- Electronic Tax Filing in Tax Reporting Framework
- Customizing Localized Tax Returns
- One Stop Shop
- Tax Reporting Framework Error Codes
- Known Limitations of Tax Reporting Framework