diff --git a/assets/attachments/programming-standard/businessEngineJobsLogging.png b/assets/attachments/programming-standard/businessEngineJobsLogging.png
new file mode 100644
index 0000000..df09f1b
Binary files /dev/null and b/assets/attachments/programming-standard/businessEngineJobsLogging.png differ
diff --git a/docs/documentation/api-specification/database-api.md b/docs/documentation/api-specification/database-api.md
index ea39a93..644d970 100644
--- a/docs/documentation/api-specification/database-api.md
+++ b/docs/documentation/api-specification/database-api.md
@@ -109,6 +109,7 @@ void readRecord() {
### Read multiple records
To read multiple records a database action should be defined along with a `Closure>` that defines how each read record
will be processed/used.
+When reading multiple records, the 'readAll' function including 'pageSize' should be used. 'pageSize' is referred to as 'nrOfRecords' in the examples below.
Example:
@@ -136,7 +137,7 @@ Closure> releasedItemProcessor = { DBContainer container ->
### Read with selection
Database API has support for expressions and filters, to use the feature, an instance of `ExpressionFactory` should be
-retrieved to build the filter and then used to build the DBAction. The rest is identical to a normal operation.
+retrieved to build the filter and then used to build the DBAction. The rest is identical to a normal operation.
Example:
@@ -154,8 +155,10 @@ public void main() {
.build();
DBContainer container = query.getContainer();
container.set("MMCONO", currentCompany);
- container.set("MMSTAT", "20");
- query.readAll(container, 2, releasedItemProcessor);
+ container.set("MMSTAT", "20");
+ int nrOfKeys = 2;
+ int nrOfRecords = mi.getMaxRecords() <= 0 || mi.getMaxRecords() >= 10000? 10000: mi.getMaxRecords();
+ query.readAll(container, nrOfKeys, nrOfRecords, releasedItemProcessor);
}
Closure> releasedItemProcessor = { DBContainer container ->
@@ -191,7 +194,9 @@ public void main() {
mi.write();
}
}
- query.readAll(container, 3, readCallback)
+ int nrOfKeys = 3;
+ int nrOfRecords = mi.getMaxRecords() <= 0 || mi.getMaxRecords() >= 10000? 10000: mi.getMaxRecords();
+ query.readAll(container, nrOfKeys, nrOfRecords, readCallback)
}
```
@@ -235,7 +240,9 @@ void deprecateItems() {
DBContainer container = query.getContainer();
container.set("MMCONO", currentCompany);
container.set("MMSTAT", "20");
- query.readAllLock(container, 2, updateCallBack);
+ int nrOfKeys = 2;
+ int nrOfRecords = mi.getMaxRecords() <= 0 || mi.getMaxRecords() >= 10000? 10000: mi.getMaxRecords();
+ query.readAllLock(container, nrOfKeys, nrOfRecords, updateCallBack);
}
Closure> updateCallBack = { LockedResult lockedResult ->
diff --git a/docs/documentation/api-specification/interactive-api.md b/docs/documentation/api-specification/interactive-api.md
index d76f470..02ecd89 100644
--- a/docs/documentation/api-specification/interactive-api.md
+++ b/docs/documentation/api-specification/interactive-api.md
@@ -58,7 +58,7 @@ public class SampleExtension extends ExtendM3Trigger {
public void main() {
String WHLO = interactive.display.fields.WRWHLO
if(WHLO == '001'){
- interactive.display.showOkDialog("You are in warehouse " + WHLO);
+ interactive.showOkDialog("You are in warehouse " + WHLO);
}
}
}
diff --git a/docs/documentation/limitations.md b/docs/documentation/limitations.md
index dcd03a6..f253a9a 100644
--- a/docs/documentation/limitations.md
+++ b/docs/documentation/limitations.md
@@ -25,7 +25,7 @@ General XtendM3 limitations.
### M3 Program calls
It is not possible to call M3 programs from an extension. Only M3 APIs can be called from extensions.
-### Database read on user-defined sorting orders
+### Database read on user-defined sorting orders
It is not possible to build query on standard tables using user-defined sorting order or index created using CRS021.
## Extending
diff --git a/docs/documentation/programming-standard.md b/docs/documentation/programming-standard.md
index 72f7bd3..adfdebf 100644
--- a/docs/documentation/programming-standard.md
+++ b/docs/documentation/programming-standard.md
@@ -20,26 +20,29 @@ Guideline for writing secure, optimized and scalable extensions.
---
## Extension Types
-Extension can be created in any of the four types:
+There are five types of extensions, below is a short description of each type.
### Trigger Extensions
-Trigger extensions are used to hook or inject our own code in a specific method in M3 Java through an extension point. These extensions extend the ExtendM3Trigger class.
+Trigger extensions are used to hook or inject our own code in a specific method in M3 BE via extension points.
-### Utility Extensions
-Utility extensions are used to create an extension program with collection of methods that can be called in other extensions. These extensions extend the ExtendM3Utility class.
+### Transaction Extensions
+Transaction extensions are used to create custom M3 API transactions.
-`utility.call(String utility, String method, Object... arguments)`
+### Batch Extensions
+Batch extensions are used for long-running jobs and can be executed/scheduled via *SHS010MI*.
-### Transaction Extensions
-Transaction Extensions are used to create custom M3 API transactions. These extensions extend the ExtendM3Transaction class.
+### Utility Extensions
+Utility extensions are used to create an extension program with collection of methods that can be called in other extensions.
+`utility.call(String utility, String method, Object... arguments)`
### Table Extensions
-Table Extensions are used to create dynamic table. In BE 15.x version, this is the equivalent of the MAK custom tables. It is possible to add columns, assign data types, unique keys, and indices.
+Table extensions, or Dynamic Tables, are custom database tables.
+It is possible to add columns, assign data types, unique keys, and indexes.
## Indentation and Formatting
-Tab size: 2 spaces
-Indent size: 2 spaces
-Continuation indent: 4 spaces
+Tab size: 2 spaces
+Indent size: 2 spaces
+Continuation indent: 4 spaces
Charset: utf-8
XtendM3 uses Groovy programming and ending code lines with semicolons are optional.
@@ -48,7 +51,8 @@ XtendM3 uses Groovy programming and ending code lines with semicolons are option
### Extensions
Extension names should be a valid Java class name.
-There is no restriction on extension names and will run with no error when compiled. However, when creating extensions, these naming conventions should be followed for readability and maintenance:
+There are no restrictions on extension names, and they will run with no error when compiled.
+However, when creating extensions the naming conventions below should be followed for readability and maintenance.
__Module__
`SMS`
@@ -83,19 +87,21 @@ __Description__
#### Trigger Extensions
-`Format: ___`
+Format: `___`
+
+Example:
Type | Program | Extension name
------------- | -------------| -------------
-Interactive | APS450 | FIM_IW_1784_ValidateInvoice
+------------ | -------------| -------------
+Interactive| APS450| FIM_IW_1784_ValidateInvoice
Fnc Batch| APS450Fnc|FIM_Fnc_1784_ValidateInvoice
Batch| OOLINEPI| SLS_Batch_1784_ValidateCO
MI Batch| MMS100MI| SCE_MI_1784_ValidateDO
#### Utility Extensions
-`Format: Utils`
+Format: `Utils`
-DateUtils
+Example: `DateUtils`
#### Transaction Extensions
- EXT9XXMI is reserved for standard extensions and should not be used
@@ -138,12 +144,46 @@ validateType
- Variables should be in lowerCamelCase and constants in CAPITAL_CONSTANTS
## Extension Structure
-- Global variables and main method are declared at the beginning of the program as compared to M3 Java where these are declared at the end of the program
-``
+- Global variables and main method are declared at the beginning of the program as compared to M3 Java where these are declared at the end of the program:
+```groovy
+ public class ExampleTransaction extends ExtendM3Transaction {
+ private final DatabaseAPI database;
+ private final LoggerAPI logger;
+
+ private Integer globalInteger;
+ private String globalString;
+
+ public ExampleExtension(DatabaseAPI database, LoggerAPI logger) {
+ this.database = database;
+ this.logger = logger;
+ }
+
+ public void main() {
+ globalInteger = 111;
+ globalString = "example"
+ }
+ }
+```
### Utility Extensions
- Utility extensions cannot contain global variables
-- It is not possible to declare and create instance of API in utility extensions, instead API should be passed as parameters in utility methods ``
+- It is not possible to declare and create instance of API in utility extensions, instead API should be passed as parameters in utility methods:
+```groovy
+ public class ExampleUtility extends ExtendM3Utility {
+
+ public String exampleGet(String miParamter, MICallerAPI miCaller) {
+
+ String returnValue;
+ Map parameters = ["EXPA": miParameter];
+
+ Closure> response = { Map response ->
+ returnValue = response.get("EXPA");
+ }
+
+ miCaller.call("EXT000MI", "Get", parameters, response);
+ }
+ }
+```
## Programming Practices
### Logging
@@ -151,7 +191,9 @@ validateType
- It is encouraged to add logs in extensions to help in debugging and troubleshooting issues particularly on non-development environments (TRN, PRD) where there is restriction on modifying extensions
- Use proper level of logging – If extension is to be deployed in PRD, it should be limited to DEBUG level. Using INFO/WARNING/ERROR/TRACE on production environment can cause huge log size and impact MT Cloud environment
- Avoid logging values of all returned fields in a database query
-- Logs can be accessed through Administration tool - Business Engine Jobs ``
+- Logs can be accessed through Administration tool - Business Engine Jobs
+
+
#### Log levels
- warning
@@ -161,9 +203,10 @@ validateType
- debug
### Database Access
-- It is recommended to use standard API if transactions are available instead of direct database access on WRITE/UPDATE/DELETE where the program uses multiple tables to update. This could cause corrupt data if any on the logic, validation, or database update was missed or incorrectly updated
+- It is recommended to use standard APIs if transactions are available instead of direct database access on WRITE/UPDATE/DELETE where the program uses multiple tables to update. This could cause corrupt data if any on the logic, validation, or database update was missed or incorrectly updated
- Make sure when using readLock that it is released at the end of the extension to avoid blocking another program access
-- When reading table with partial keys, verify that this will not return too many records
+- When reading table with partial keys, verify that this will not return too many records. Include _nrOfRecords_ parameter when performing _readAll_ API calls
+ - It is not allowed read more than 10,000 records per read. If there is a need to read more than 10,000 records, contact the review team with a good reasoning as to why it's needed
- When reading table, if possible, specify only the required column fields and avoid selectAllFields on querying tables particularly on tables which contains many fields
### Loops
@@ -173,7 +216,7 @@ validateType
#### In Memory
- SessionAPI is used to store information in key-value mapping that can be accessed between multiple extensions in the same session
- Make sure to use unique identifier as key names to avoid conflicts with other extensions when adding content to the cache
-- Reassess when there is too many information that needs to be stored in a session. It may be more practical to add the information using XtendM3 tables instead
+- Reassess when there is too much information that needs to be stored in a session. It may be more practical to add the information using XtendM3 tables instead
#### In Customer Extension Table
- Customer extension table refers to CUGEX1, CUGEX2 and CUGEX3 tables and can be accessed through standard API, CUSEXTMI
@@ -181,12 +224,47 @@ validateType
#### In Dynamic Database
- Dynamic Database or XtendM3 table is the equivalent of MAK custom table
-- Table name uses prefix EXT and can have specific columns and logical keys
-`to add example`
+- Table name uses prefix EXT and can have specific columns and logical keys
+ ```groovy
+ void read() {
+ DBAction query = database.table("EXTMIT")
+ .index("00")
+ .selection("EXITNO", "EXITDS")
+ .build();
+ DBContainer container = query.createContainer();
+ container.set("EXCONO", 999);
+ container.set("EXDIVI", "GGG");
+ container.set("EXSTAT", "30");
+ container.set("EXRESP", "KFHAKANSSON");
+ if(query.read(container)) {
+ String itemNumber = container.get("EXITNO");
+ String itemDescription = container.get("EXITDS");
+ mi.outData.put("RES1", itemNumber);
+ mi.outData.put("RES2", itemDescription);
+ mi.write();
+ }
+ }
+ ```
#### In Text/XML/JSON File
- TextFilesAPI is the equivalent of MvxTextFile in M3 Java which can be used to read, write, or delete file
-`to add example`
+ ```groovy
+ public void readRecord(String folder, String record) {
+ textFiles.open(folder);
+ textFiles.read(record, "UTF-8", readFile);
+ }
+
+ Closure> readFile = { BufferedReader reader ->
+ List header = resolveFields(reader.readLine());
+ while((line = reader.readLine()) != null) {
+ reader.println(line);
+ }
+ readResult = header.toString();
+ mi.outData.put("RESU", readResult);
+ mi.write();
+ }
+ ```
+ - More examples available here: [TextFilesAPI](https://infor-cloud.github.io/xtendm3/docs/documentation/api-specification/textfiles-api)
### API Call
- M3 API can be run using IonAPI or MICallerAPI
@@ -207,10 +285,14 @@ validateType
### Date and Time
- Use classes [LocalDate](http://docs.groovy-lang.org/latest/html/groovy-jdk/java/time/LocalDate.html), [LocalTime](http://docs.groovy-lang.org/latest/html/groovy-jdk/java/time/LocalTime.html), [LocalDateTime](http://docs.groovy-lang.org/latest/html/groovy-jdk/java/time/LocalDateTime.html) if required to retrieve current date or manipulate date or time
`int entryDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")).toInteger()`
-`int entryTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HHmmss")).toInteger()`
+`int entryTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HHmmss")).toInteger()`
+
+### Variable Types
+- It is not allowed to use *__def__* statements in XtendM3, the type should be known
+ - If the type is unknown, use *__Object__*
## Testing
-Extension approval requires submission of approved Unit test and Functional test documents
+Extension approval requires submission of approved Unit test and Functional test documents
### Local
- Extensions can be locally tested using [XtendM3 SDK](https://github.com/infor-cloud/xtendm3-sdk-java) which provides the interfaces for the internal APIs available to XtendM3 Extensions. This SDK can be used to build, test and debug Extensions locally without needing to have any M3 environment up and running
@@ -219,7 +301,14 @@ Extension approval requires submission of approved Unit test and Functional test
### Live
- If the program extension has been activated in the tenant, it automatically runs and the changes to the program are seen by the users. During unit testing, it is recommended that the extension should execute only for specific users until it is verified working to avoid interrupting other users when running the same program
- Provided is an example of validating extension to run for specific user/s
-`to add example`
+ ```groovy
+ private boolean isEnabled() {
+ if (program.getUser() != "USERNAME") {
+ return false
+ }
+ return true
+ }
+ ```
## Version Controlling
More details are provided at [Version Controlling](https://infor-cloud.github.io/xtendm3/docs/documentation/version-controlling/) page
@@ -234,5 +323,24 @@ Refer to the example of an [XtendM3 Extension Repository](https://github.com/inf
Any git provider that the customer prefers GitHub, GitLab, Bitbucket and etc.
## Documentation
-- Extension JavaDoc on top of extension classes is required
-- Extension Methods JavaDoc on top of methods (except for main) is recommended
+- Extension JavaDoc on top of extension classes is required
+ - Example:
+
+ ```groovy
+ /****************************************************************************************
+ Extension Name: EXT000MI/transactionName
+ Type: ExtendM3Transaction
+ Script Author:
+ Date:
+ Description:
+ * Description of script functionality
+
+ Revision History:
+ Name Date Version Description of Changes
+ Revision Author 2022-01-01 1.0 Descriptive text here
+ Revision Author 2022-02-02 1.1 Outlining what's been updated
+ Revision Author 2022-03-03 1.2 In the current revision
+ ******************************************************************************************/
+ ```
+
+- Extension Methods JavaDoc on top of methods (except for main) is recommended