First pass conversion of NAV Patterns
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 22 KiB |
146
content/NAVPatterns/1-sensitive-data-encapsulation/index.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
+++
|
||||
title = "1-sensitive-data-encapsulation.md"
|
||||
weight = 120
|
||||
+++
|
||||
## Sensitive Data Encapsulation
|
||||
|
||||
_By Bogdana Botez at Microsoft Development Center Copenhagen_
|
||||
|
||||
_
|
||||
_
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
**
|
||||
**
|
||||
|
||||
**Aliases:** Encapsulation, Separation of Concerns \[1\]
|
||||
|
||||
**
|
||||
**
|
||||
|
||||
**Context**: You want to store and protect sensitive data which already exists in a system, but it is not clear which data needs protection and how to store it.
|
||||
|
||||
**Problem**: Sensitive data is scattered and mixed with other data in various parts of the system (passwords residing in the same table with non-sensitive data, part of the private information might be stored in files, hardcoded text constants, hardcoded info as part of the code etc.).
|
||||
|
||||
****
|
||||
|
||||
**Forces:**
|
||||
|
||||
* **Obscurity:** sensitive data mixed with other data and code makes it hard to have an overview of what needs to be protected.
|
||||
* **Effort:** the protection mechanisms needs to be applied multiple times, once for each location of the sensitive data.
|
||||
* **Diversity:** different data storage mediums can require different protection solutions.
|
||||
* **Cohesiveness:** code, low importance data, highly sensitive data are all monolithically mixed which might make it hard to select and protect only what is important.
|
||||
* **Low** **Performance**: protecting all data can slow down the system.
|
||||
|
||||
**Solution:** Extract and separate sensitive data into a single known repository.
|
||||
|
||||
To apply this pattern in Dynamics NAV, a table structure similar to Table 1261 Service Password can be used. The Service Password table also contains additional functionality which will help to further apply related patterns like [**Access Control**][anchor1] and [**Encryption**][anchor2].
|
||||
|
||||
Figure 1 describes the definition of a table which is already available in Dynamics NAV. This table can be used for storing sensitive data. As a minimum, the table only needs two fields:
|
||||
|
||||
1. The key is of type GUID (Globally Unique Identifier), which is a 128-bit value consisting of multiple groups of hexadecimal digits \[2\]. Each key needs to be unique and will be used for storing and retrieving the protected information.
|
||||
2. The value (the actual data to be encapsulated) is of type BLOB (Binary Large OBject) \[3\], which contains the encrypted or un-encrypted data (for encryption, see the related [**Encryption** ][anchor2]pattern).
|
||||
|
||||
[![Figure 1- Example definition, table used for Data Encapsulation][image1]][anchor3]
|
||||
|
||||
_Figure 1- Example definition, table used for **Sensitive** **Data** **Encapsulation**_
|
||||
|
||||
**
|
||||
**
|
||||
|
||||
Let's use a software system (which can be a Dynamics NAV extension or customization). Chances are that the system will look similar to the one described in part 1 of Figure 2: there is data handled in various places of the system, on various storage solutions. Intertwined with normal data, there is sensitive data. For example: Dynamics CRM (Customer Relationship Management) connection information could be all stored in one table, and consists of the connection URI (normal data), enabled/disabled status (normal data), and connection password (sensitive data).
|
||||
|
||||
Figure 2 illustrates the system before and after applying the **Sensitive Data Encapsulation** pattern.
|
||||
|
||||
* Before: picture part 1 shows the initial system, where most likely the data is scattered across the system. Furthermore, it is stored in different mediums, like
|
||||
* In a table (possible together with low-importance data)
|
||||
* Hardcoded (in Dynamics NAV, it can be a text constant, or just plainly typed in the code)
|
||||
* In files
|
||||
|
||||
Furthermore, actors which have to interact with the data, need to remember where to find each piece of information and how to retrieve it.
|
||||
|
||||
* After: the part 2 of Figure 2 shows the same system after application of **Sensitive Data Encapsulation**. Now, all data is stored in one place, and all actors which interact with it can read it from the unique location.
|
||||
|
||||
[![ ][image2]][anchor4]
|
||||
|
||||
_Figure 2 - Data access before and after **Sensitive** **Data** **Encapsulation.**_
|
||||
|
||||
.
|
||||
|
||||
**Benefits:**
|
||||
|
||||
* **Clarity:** when all sensitive data is encapsulated in one place, it is clear which is the information that needs to be protected.
|
||||
* **Simplicity:** easier to protect just a limited number of known resources when they are grouped.
|
||||
* **Homogeneity: **the same protection can be applied to all data, since it is stored in the same place.****
|
||||
* **Separation of concerns: **treat each section of the computer program differently, by separating it and clearly addressing its own requirements and limitations. \[1\]
|
||||
* **Performance**: data protection techniques like **Access Control** and **Encryption **can now be applied only to the sensitive data (not to everything), which improves the performance of the system.
|
||||
|
||||
**Drawbacks:**
|
||||
|
||||
* **Single point of failure:** a maliciously intended actor has no longer a need to reverse engineer the places where important data is stored. In the unwanted situation when this actor would have already obtained access to the system, they can more easily locate the sensitive information. This is a step towards information disclosure, but mechanisms like [**Encryption** ][anchor2]and logging can provide further protection.
|
||||
* **Limited Text Length**: there is a limit on how long the encrypted text can be. This limit is imposed by the OS encryption service and it depends on the composition of the text as well as on the system specifics. In NAV, we had implemented the Encrypted Text for text values of max 250 chars, which is enough to cover passwords, person ID numbers, credit card info, but it might turn insufficient in other future scenarios.
|
||||
* **Nomenclature**: the name of the table **Service Password** is too specific, since it started by being used for passwords, but it has the capability and it now contains other sensitive data like API Keys, credit card numbers etc.
|
||||
|
||||
**References**
|
||||
|
||||
\[1\]
|
||||
|
||||
"Wikipedia," \[Online\]. Available: https://en.wikipedia.org/wiki/Separation\_of\_concerns.
|
||||
|
||||
\[2\]
|
||||
|
||||
"GUID Structure," \[Online\]. Available: https://msdn.microsoft.com/en-us/library/aa373931(VS.85).aspx.
|
||||
|
||||
\[3\]
|
||||
|
||||
"Wikipedia," \[Online\]. Available: https://en.wikipedia.org/wiki/Binary\_large\_object.
|
||||
|
||||
\[4\]
|
||||
|
||||
E. Wauters, "How Do I: Manage Companies in Microsoft Dynamics NAV 2013 R2".
|
||||
|
||||
\[5\]
|
||||
|
||||
Microsoft, "Multitenant Deployment Architecture," Microsoft, \[Online\]. Available: https://msdn.microsoft.com/en-us/library/dn271675(v=nav.90).aspx.
|
||||
|
||||
\[6\]
|
||||
|
||||
B. Botez, "Setup Table design pattern," Microsoft, 2013\. \[Online\]. Available: https://community.dynamics.com/nav/w/designpatterns/76.setup-table. \[Accessed 31 07 2016\].
|
||||
|
||||
\[7\]
|
||||
|
||||
"Shotgun Surgery," \[Online\]. Available: https://en.wikipedia.org/wiki/Shotgun\_surgery. \[Accessed 31 07 2016\].
|
||||
|
||||
\[8\]
|
||||
|
||||
M. Fowler, Refactoring: Improving the design of existing code, Addison Wesley, 1999\.
|
||||
|
||||
\[9\]
|
||||
|
||||
"Masking out," \[Online\]. Available: https://en.wikipedia.org/wiki/Data\_masking\#Masking\_out. \[Accessed 29 7 2016\].
|
||||
|
||||
\[10\]
|
||||
|
||||
"Key Vault," Microsoft, \[Online\]. Available: https://azure.microsoft.com/en-us/services/key-vault/.
|
||||
|
||||
\[11\]
|
||||
|
||||
"How to: Configure SSL to Secure the Connection to Microsoft Dynamics NAV Web Client," Microsoft, \[Online\]. Available: https://msdn.microsoft.com/en-us/library/hh167264(v=nav.90).aspx. \[Accessed 2 8 2016\].
|
||||
|
||||
\[12\]
|
||||
|
||||
"sniffer," \[Online\]. Available: http://compnetworking.about.com/od/networksecurityprivacy/g/bldef\_sniffer.htm. \[Accessed 02 08 2016\].
|
||||
|
||||
|
||||
|
||||
[anchor0]: Logo-_2D00_-Protected-Data-Encapsulation.png
|
||||
[anchor1]: /nav/w/designpatterns/277.3-single-point-of-acces
|
||||
[anchor2]: /nav/w/designpatterns/276.2-data-encryption
|
||||
[anchor3]: Data-Encapsulation-_2D00_-figure-1.png
|
||||
[anchor4]: Multi-_2D00_-1-2.JPG
|
||||
|
||||
|
||||
[image0]: Logo-_2D00_-Protected-Data-Encapsulation.png
|
||||
[image1]: Data-Encapsulation-_2D00_-figure-1.png
|
||||
[image2]: Multi-_2D00_-1-2.JPG
|
||||
17
content/NAVPatterns/2-anti-patterns/index.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
+++
|
||||
title = "2-anti-patterns.md"
|
||||
weight = 130
|
||||
+++
|
||||
Some of the software development practices, had **not** stood the test of time. Despite that, some are still being used today by developers everywhere.
|
||||
|
||||
"An **anti-pattern** (or **antipattern**) is a common response to a recurring problem that is usually ineffective and risks being highly counterproductive" (from [Wikipedia][anchor0])
|
||||
|
||||
Since almost the beginning of the NAV Design Patterns project, we talked about documenting the anti-patterns - but never found the time. Until, spontaneously, the April 1st 2015 article had practically wrote itself in a couple of hours, with priceless contributions coming from Andreas, Elly, Nikola - and last but not least, waldo.
|
||||
|
||||
Best regards,
|
||||
|
||||
Bogdana Botez
|
||||
|
||||
|
||||
|
||||
[anchor0]: http://en.wikipedia.org/wiki/Anti-pattern
|
||||
BIN
content/NAVPatterns/2-data-encryption/Encryption-_2D00_-1.JPG
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
content/NAVPatterns/2-data-encryption/Encryption-_2D00_-2.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
content/NAVPatterns/2-data-encryption/Encryption-_2D00_-3.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
content/NAVPatterns/2-data-encryption/Encryption-_2D00_-4.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
content/NAVPatterns/2-data-encryption/Encryption-_2D00_-5.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
content/NAVPatterns/2-data-encryption/Logo-_2D00_-Encryption.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
content/NAVPatterns/2-data-encryption/Multi-_2D00_-1-2-3.JPG
Normal file
|
After Width: | Height: | Size: 25 KiB |
230
content/NAVPatterns/2-data-encryption/index.md
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
+++
|
||||
title = "2-data-encryption.md"
|
||||
weight = 140
|
||||
+++
|
||||
## Encryption
|
||||
|
||||
_By Bogdana Botez at Microsoft Development Center Copenhagen_
|
||||
|
||||
__
|
||||
|
||||
_[![ ][image0]][anchor0]
|
||||
_
|
||||
|
||||
****
|
||||
|
||||
**Context**: After applying [**Sensitive Data Encapsulation**][anchor1], all sensitive data is gathered in a known place in the database. This makes it possible to apply further protection best practices.
|
||||
|
||||
**Problem**: If any non-authorized actor manages to get access to a copy of the database, the sensitive data is immediately available in clear-text.
|
||||
|
||||
**
|
||||
**
|
||||
|
||||
**Forces:**
|
||||
|
||||
* **Accessibility:** anyone who managed to steal a copy of the database can at once read the sensitive information.
|
||||
|
||||
**Solution:** Encrypt sensitive data. Dynamics NAV offers a simple mechanism for data encryption, to be used by NAV developers.
|
||||
|
||||
Figure 1 adds one more step (in continuation of the figure in pattern **[Sensitive Data Encapsulation][anchor2]**), in which the first two panels describe how sensitive data has all been gathered in one known place. This makes the current pattern, **Encryption**, much easier -- since now data needs to be encrypted in only one place. The last panel of Figure 1 shows the system after the next step, **Encryption**, has been implemented. This shows the iterative process of applying patterns and making the code better step by step.
|
||||
|
||||
[![ ][image1]][anchor3]
|
||||
|
||||
_Figure 1- **Encryption** becomes easier after first applying [**Sensitive Data Encapsulation**][anchor1]._
|
||||
|
||||
****
|
||||
|
||||
**Usage**: in Dynamics NAV, codeunit 1266 Encryption Management offers an API for encryption of data. The available functionality is described in Table 1\.
|
||||
|
||||
Table 1- Encryption functionality in Dynamics NAV (found in codeunit 1266 Encryption Management)
|
||||
|
||||
**Procedure******
|
||||
|
||||
**Description******
|
||||
|
||||
**EnableEncryption**
|
||||
|
||||
Confirms with the user before enabling encryption in all companies of the current database.
|
||||
|
||||
**EnableEncryptionSilently**
|
||||
|
||||
Enables encryption without UI interaction (to be used for web services).
|
||||
|
||||
**DisableEncryption**
|
||||
|
||||
Disables encryption. Has a boolean "Silent" parameter which enables/disables UI interaction.
|
||||
|
||||
**Encrypt**
|
||||
|
||||
Checks that encryption is possible before encrypting the given text.
|
||||
|
||||
**Decrypt**
|
||||
|
||||
Checks that encryption is possible before decrypting the given text.
|
||||
|
||||
**ExportKey**
|
||||
|
||||
If encryption is possible, it shows a confirmation dialog to confirm the export of the encryption key, and proceeds to saving it to a location chosen by the user.
|
||||
|
||||
**ImportKey**
|
||||
|
||||
Imports the encryption key from a user chosen location.
|
||||
|
||||
**ChangeKey**
|
||||
|
||||
Changes the encryption key.
|
||||
|
||||
**IsEncryptionEnabled**
|
||||
|
||||
Returns TRUE is encryption is enabled.
|
||||
|
||||
**IsEncryptionPossible**
|
||||
|
||||
Check is the correct key is present, which only works if encryption is enabled.
|
||||
|
||||
**DeleteEncryptedDataInAllCompanies**
|
||||
|
||||
Confirms through UI and if the user agrees, deletes all data stored in the Service Password table for all companies in the current database. At the end, it deletes the encryption key.
|
||||
|
||||
**User confirmation of encryption**
|
||||
|
||||
Ideally, encryption should be enabled by default.
|
||||
|
||||
However, in some versions of Dynamics NAV, there are places where any user who happens to hit a sensitive field (like a password) is asked if they want to encrypt or not. If user confirmation is needed, then let only the security administrators decide (not any random user).
|
||||
|
||||
To ask the security administrator if they want to enable encryption, plug the code below into the OnValidate trigger of the data field which is to be encrypted. This trigger will then get executed each time there's a change on the password field. For example, if the user is entering a password, then add a call to this procedure to Password -- OnValidate() trigger:
|
||||
|
||||
EncryptionIsNotActivatedQst@1001 : TextConst 'ENU=Data encryption is not activated. It is recommended that you encrypt data. \\Do you want to open the Data Encryption Management window?';
|
||||
|
||||
LOCAL PROCEDURE CheckEncryption@6();
|
||||
|
||||
BEGIN
|
||||
|
||||
IF NOT ENCRYPTIONENABLED THEN
|
||||
|
||||
IF CONFIRM(EncryptionIsNotActivatedQst) THEN
|
||||
|
||||
PAGE.RUN(PAGE::"Data Encryption Management");
|
||||
|
||||
END;
|
||||
|
||||
The code above will guide the user on encrypting, storing the encryption key and choosing a safe password, as follows. Consider for example that NAV is used by a small business, where Stan is the business owner. Hence, Stan has access to all NAV setup and security decisions. Stan wants to enable a connection between NAV and CRM. In NAV, he opens page CRM Connection Setup and enters the URL, user name and password for CRM (Figure 10). When Stan leaves the password field, if data encryption is not enabled, then he sees the confirmation message in Figure 2\.
|
||||
|
||||
[![ ][image2]][anchor4]
|
||||
|
||||
_Figure 2- The administrator is advised to enable encryption._
|
||||
|
||||
Now Stan becomes aware that there is no encryption and has the opportunity to enable it. If he clicks on "Yes", Stan will be taken to the Data Encryption Management page (Figure 3).
|
||||
|
||||
[![ ][image3]][anchor5]
|
||||
|
||||
_Figure 3- Data Encryption Management page_
|
||||
|
||||
Stan can now choose "Enable Encryption" from the ribbon which informs him that an encryption key will be created (Figure 4).
|
||||
|
||||
[![ ][image4]][anchor6]
|
||||
|
||||
_Figure 4- The administrator is encouraged to save a copy of the encryption key._
|
||||
|
||||
Stan has the option to save the encryption key in a safe location. He invokes the action Enable Encryption and is guided forward (Figure 5).
|
||||
|
||||
[![ ][image5]][anchor7]
|
||||
|
||||
_Figure 5- The encryption key needs to be protected by a password._
|
||||
|
||||
Stan chooses a password, which in this implementation requires minimum 8 characters with at least one uppercase character, one lowercase character and one digit. If the chosen password is too weak, Stan is given information on the expected complexity (Figure 6).
|
||||
|
||||
[![ ][image6]][anchor8]
|
||||
|
||||
_Figure 6- The administrator chooses a strong password._
|
||||
|
||||
In the end, Stan chooses a location for the encryption key on disk, in a standard file save dialog. The layout of the file save dialog depends on the display target used (web browser, rich client etc.).
|
||||
|
||||
**NAV Usages. **Encryption examples can be found in NAV in the following places:
|
||||
|
||||
* Page 1260 Bank Data Conv. Service Setup
|
||||
* Page 1270 OCR Service Setup
|
||||
* Page 5330 CRM Connection Setup
|
||||
* Table 1275 Doc. Exch. Service Setup
|
||||
|
||||
**Benefits:**
|
||||
|
||||
* **Protection**: even if the database has been compromised, the sensitive data is encrypted hence not immediately accessible. Combined with best user practices like setting strong passwords, encryption can prove to be a very strong protection mechanism.
|
||||
|
||||
**Anti-patterns:**
|
||||
|
||||
* **Performance:** Do not encrypt everything, because it will have a performance impact on the system. Only important information should be encrypted.
|
||||
* **System calls and "Do It Yourself" solutions**: Although system calls for encryptions are available in NAV (ENCRYPT, DECRYPT and <action\>ENCRYPTIONKEY), unless there is no other way, refrain from using them directly. Use instead he API in codeunit 1266 Encryption Management, which is safer to use, because it protects against common mistakes (like attempting to encrypt an already encrypted string, enabling encryption in only one company which makes data not usable in another on the same tenant etc.). Use the [**Single Point of Access **][anchor9]pattern to handle sensitive data which needs to be encrypted.
|
||||
* **System-level encryption.** The similar codeunit API 1803 Encrypted Key/Value Management is not intended to be reused by partner NAV developers. Normal NAV users do not have permission to access this resource. This stores sensitive data to be accessed by system NAV functionality.
|
||||
|
||||
**References**
|
||||
|
||||
\[1\]
|
||||
|
||||
"Wikipedia," \[Online\]. Available: https://en.wikipedia.org/wiki/Separation\_of\_concerns.
|
||||
|
||||
\[2\]
|
||||
|
||||
"GUID Structure," \[Online\]. Available: https://msdn.microsoft.com/en-us/library/aa373931(VS.85).aspx.
|
||||
|
||||
\[3\]
|
||||
|
||||
"Wikipedia," \[Online\]. Available: https://en.wikipedia.org/wiki/Binary\_large\_object.
|
||||
|
||||
\[4\]
|
||||
|
||||
E. Wauters, "How Do I: Manage Companies in Microsoft Dynamics NAV 2013 R2".
|
||||
|
||||
\[5\]
|
||||
|
||||
Microsoft, "Multitenant Deployment Architecture," Microsoft, \[Online\]. Available: https://msdn.microsoft.com/en-us/library/dn271675(v=nav.90).aspx.
|
||||
|
||||
\[6\]
|
||||
|
||||
B. Botez, "Setup Table design pattern," Microsoft, 2013\. \[Online\]. Available: https://community.dynamics.com/nav/w/designpatterns/76.setup-table. \[Accessed 31 07 2016\].
|
||||
|
||||
\[7\]
|
||||
|
||||
"Shotgun Surgery," \[Online\]. Available: https://en.wikipedia.org/wiki/Shotgun\_surgery. \[Accessed 31 07 2016\].
|
||||
|
||||
\[8\]
|
||||
|
||||
M. Fowler, Refactoring: Improving the design of existing code, Addison Wesley, 1999\.
|
||||
|
||||
\[9\]
|
||||
|
||||
"Masking out," \[Online\]. Available: https://en.wikipedia.org/wiki/Data\_masking\#Masking\_out. \[Accessed 29 7 2016\].
|
||||
|
||||
\[10\]
|
||||
|
||||
"Key Vault," Microsoft, \[Online\]. Available: https://azure.microsoft.com/en-us/services/key-vault/.
|
||||
|
||||
\[11\]
|
||||
|
||||
"How to: Configure SSL to Secure the Connection to Microsoft Dynamics NAV Web Client," Microsoft, \[Online\]. Available: https://msdn.microsoft.com/en-us/library/hh167264(v=nav.90).aspx. \[Accessed 2 8 2016\].
|
||||
|
||||
\[12\]
|
||||
|
||||
"sniffer," \[Online\]. Available: http://compnetworking.about.com/od/networksecurityprivacy/g/bldef\_sniffer.htm. \[Accessed 02 08 2016\].
|
||||
|
||||
|
||||
|
||||
[anchor0]: Logo-_2D00_-Encryption.png
|
||||
[anchor1]: /nav/w/designpatterns/275.1-sensitive-data-encapsulation
|
||||
[anchor2]: /nav/w/designpatterns/275.sensitive-data-encapsulation
|
||||
[anchor3]: Multi-_2D00_-1-2-3.JPG
|
||||
[anchor4]: Encryption-_2D00_-1.JPG
|
||||
[anchor5]: Encryption-_2D00_-2.png
|
||||
[anchor6]: Encryption-_2D00_-3.png
|
||||
[anchor7]: Encryption-_2D00_-4.png
|
||||
[anchor8]: Encryption-_2D00_-5.png
|
||||
[anchor9]: /nav/w/designpatterns/277.3-single-point-of-acces
|
||||
|
||||
|
||||
[image0]: Logo-_2D00_-Encryption.png
|
||||
[image1]: Multi-_2D00_-1-2-3.JPG
|
||||
[image2]: Encryption-_2D00_-1.JPG
|
||||
[image3]: Encryption-_2D00_-2.png
|
||||
[image4]: Encryption-_2D00_-3.png
|
||||
[image5]: Encryption-_2D00_-4.png
|
||||
[image6]: Encryption-_2D00_-5.png
|
||||
25
content/NAVPatterns/3-cal-coding-guidelines/index.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
+++
|
||||
title = "3-cal-coding-guidelines.md"
|
||||
weight = 150
|
||||
+++
|
||||
We've decided to publish our current C/AL coding guidelines. They are actual, as per January 2015 when this is published (but might fall out of sync as time goes by).
|
||||
|
||||
You can also [download the C/AL coding guidelines as pdf,][anchor0] all in one document. In contrast, on this wiki, the coding guidelines are published individually. The reason is: give you the chance to comment and share your oppinion on each one. Thanks to [waldo][anchor1] for this idea and for helping out.
|
||||
|
||||
The guidelines are debatable - and it is good when they are generating debate. There is variation of opinion on the rules at Microsoft too. The plan is to simply expose what we use now. And more important, to say that guidelines could be used. Debate on individual guidelines can become heated for any programming language, but the benefit of using some guidelines stays.
|
||||
|
||||
For us, those guidelines are enforced at check-in time - we are using a tool which verifies and only allows compliant check-ins. While this tool is internal and not ready to publish, we had anyways decided to open up and present the rules we use to the community, as inspiration.
|
||||
|
||||
Question: Since we're having the guidelines, how come there is still C/AL code in NAV which doesn't respect them?
|
||||
|
||||
Answer: all new C/AL code is bound to follow the guidelines (else it cannot be checked-in). However, the code that existed before the rules - it does not. We had done cleanup in a certain degree. Now we're gradually improving the old code base as we visit various objects in order to add new functionality, however chances are that code we didn't touch in a long time had remained in its old form.
|
||||
|
||||
We're looking forward to your comments. Where you can, do provide concrete examples of the alternatives, Good and Bad.
|
||||
|
||||
[watch?v=z6skKy0pkmU&list=PLhZ3P LY7CqmVszuvtJLujFyHpsVN0Uw&index=26][anchor2]
|
||||
|
||||
|
||||
|
||||
[anchor0]: https://blogs.msdn.microsoft.com/nav/2015/01/09/cal-coding-guidelines-used-at-microsoft-development-center-copenhagen "download the C/AL coding guidelines as pdf"
|
||||
[anchor1]: /members/waldo/default.aspx "waldo"
|
||||
[anchor2]: https://www.youtube.com/watch?v=z6skKy0pkmU&list=PLhZ3P-LY7CqmVszuvtJLujFyHpsVN0U_w&index=26
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 22 KiB |
114
content/NAVPatterns/3-single-point-of-access/index.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
+++
|
||||
title = "3-single-point-of-access.md"
|
||||
weight = 160
|
||||
+++
|
||||
## Single Point of Access
|
||||
|
||||
_By Bogdana Botez at Microsoft Development Center Copenhagen_
|
||||
|
||||
_[![ ][image0]][anchor0]
|
||||
_
|
||||
|
||||
__
|
||||
|
||||
**Context**: Protected data needs to be used. There are many types of entities which might attempt to use the data.
|
||||
|
||||
**Problem**: If no standard way of accessing data exists, then each entity might attempt to build its own system for handing the sensitive data. The data access layer might be implemented over and over again by each entity, without reuse of known best practices and with a lot of code duplication.
|
||||
|
||||
****
|
||||
|
||||
**Forces:**
|
||||
|
||||
* **Code duplication: **if each entity attempts to write its own routines for data access, invariably this will bring duplication.
|
||||
* **No knowledge reuse: **if a bug is found and fixed in one of the implementations, there is no guarantee that all the other implementations will be updated. For example
|
||||
* **Double-Encryption**: lack of care or knowledge could lead a NAV developer to attempt to double-encrypt strings, which would render them unusable.
|
||||
* **Multi-company configuration \[4\]**: in NAV, it is possible to store multiple companies on the same tenant \[5\] database. A developer who has not investigated such a configuration and its consequences on encryption, can attempt to encrypt data in (for example) a Setup Table \[6\] of only one company, which would make this table unusable from any unencrypted company (since the server will observe that encryption is enabled, and try to retrieve it as it were enabled for all companies and fails).
|
||||
* **"Shotgun surgery" \[7\]:** one change in the data access technique (like a new requirement to validate the user's identity before viewing any protected data), calls for updates in every single implementation of data access, if multiple implementations exist. Hence, one change of requirement triggers multiple efforts to update the product.
|
||||
|
||||
**Solution:** In Dynamics NAV the same library which offers [**Encryption**][anchor1], is also intended to be used as a **Single Point of Access**. Write code to create, read and remove sensitive data only through codeunit 1266 Encryption Management, and never directly.
|
||||
|
||||
In Figure 9 (figure numbering is continued from pattern **[Encryption][anchor2]**), the panels 1, 2 and 3 have applied the [**Sensitive Data Encapsulation**][anchor3] and [**Encryption** ][anchor1]patterns. Once data is encapsulated, no matter if it has been encrypted or not, the next pattern, **Single Point of Access**, becomes available as seen in the panels 2 and 3 of Figure 1\.
|
||||
|
||||
You can observe that, in panel 3, each usage needs to access separately the encrypted data. This means that each usage needs to implement again the encryption capabilities. This is resolved in panel 4, where all users access one common API which encrypts, hence the workload of encryption is moved from the usages, to the API, and needs to be implemented only once.
|
||||
|
||||
[![ ][image1]][anchor4]
|
||||
|
||||
_Figure 1- **Single Point of Access **pattern applied._
|
||||
|
||||
This shows how pattern application is an iterative process, where one step follows another. Refactoring \[8\] the code to apply one pattern, cleans and clarifies the code and in some cases, makes clear the possibility of further refactoring.
|
||||
|
||||
**Usage**: call the procedures available in codeunit 1266 Encryption Management to store and access sensitive data.
|
||||
|
||||
**Benefits:**
|
||||
|
||||
* **Simplicity**: only one implementation exists.
|
||||
* **Knowledge reuse**: if a bug is found and fixed, there is just one place which needs to be repaired. Therefore, there is no risk that some of the usages would still be flawed by the same bug. For example:
|
||||
* **Double encryption**: The**Single Point of Access** API already implements knowledge to avoid encryption of already encrypted strings.
|
||||
* **Multi-company**: The**Single Point of Access** API already implements knowledge for handling encryption in a multi-company setup.
|
||||
* **Easy maintenance**:****a change in specification needs only one code update.
|
||||
|
||||
**Consequences:**
|
||||
|
||||
* **Single point of failure:** A defect in the access library will affect all usages until it is found and fixed.
|
||||
|
||||
**References**
|
||||
|
||||
\[1\]
|
||||
|
||||
"Wikipedia," \[Online\]. Available: https://en.wikipedia.org/wiki/Separation\_of\_concerns.
|
||||
|
||||
\[2\]
|
||||
|
||||
"GUID Structure," \[Online\]. Available: https://msdn.microsoft.com/en-us/library/aa373931(VS.85).aspx.
|
||||
|
||||
\[3\]
|
||||
|
||||
"Wikipedia," \[Online\]. Available: https://en.wikipedia.org/wiki/Binary\_large\_object.
|
||||
|
||||
\[4\]
|
||||
|
||||
E. Wauters, "How Do I: Manage Companies in Microsoft Dynamics NAV 2013 R2".
|
||||
|
||||
\[5\]
|
||||
|
||||
Microsoft, "Multitenant Deployment Architecture," Microsoft, \[Online\]. Available: https://msdn.microsoft.com/en-us/library/dn271675(v=nav.90).aspx.
|
||||
|
||||
\[6\]
|
||||
|
||||
B. Botez, "Setup Table design pattern," Microsoft, 2013\. \[Online\]. Available: https://community.dynamics.com/nav/w/designpatterns/76.setup-table. \[Accessed 31 07 2016\].
|
||||
|
||||
\[7\]
|
||||
|
||||
"Shotgun Surgery," \[Online\]. Available: https://en.wikipedia.org/wiki/Shotgun\_surgery. \[Accessed 31 07 2016\].
|
||||
|
||||
\[8\]
|
||||
|
||||
M. Fowler, Refactoring: Improving the design of existing code, Addison Wesley, 1999\.
|
||||
|
||||
\[9\]
|
||||
|
||||
"Masking out," \[Online\]. Available: https://en.wikipedia.org/wiki/Data\_masking\#Masking\_out. \[Accessed 29 7 2016\].
|
||||
|
||||
\[10\]
|
||||
|
||||
"Key Vault," Microsoft, \[Online\]. Available: https://azure.microsoft.com/en-us/services/key-vault/.
|
||||
|
||||
\[11\]
|
||||
|
||||
"How to: Configure SSL to Secure the Connection to Microsoft Dynamics NAV Web Client," Microsoft, \[Online\]. Available: https://msdn.microsoft.com/en-us/library/hh167264(v=nav.90).aspx. \[Accessed 2 8 2016\].
|
||||
|
||||
\[12\]
|
||||
|
||||
"sniffer," \[Online\]. Available: http://compnetworking.about.com/od/networksecurityprivacy/g/bldef\_sniffer.htm. \[Accessed 02 08 2016\].
|
||||
|
||||
|
||||
|
||||
[anchor0]: Logo-_2D00_-Single-Point-of-Access.png
|
||||
[anchor1]: /nav/w/designpatterns/276.2-data-encryption
|
||||
[anchor2]: /nav/w/designpatterns/276.encryption
|
||||
[anchor3]: /nav/w/designpatterns/275.1-sensitive-data-encapsulation
|
||||
[anchor4]: Multi-_2D00_-1-2-3-4.JPG
|
||||
|
||||
|
||||
[image0]: Logo-_2D00_-Single-Point-of-Access.png
|
||||
[image1]: Multi-_2D00_-1-2-3-4.JPG
|
||||
37
content/NAVPatterns/4-get-involved/index.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
+++
|
||||
title = "4-get-involved.md"
|
||||
weight = 170
|
||||
+++
|
||||
**Spread the info**
|
||||
|
||||
NAV Design Patterns are excellent materials for training and knowledge transfer. In your company, you can help new developer get to speed with NAV by recommending them to read and then present to the team one of the most common patterns: [No. Series][anchor0], [Setup Table][anchor1] and definitely [Hooks ][anchor2]which will be a great investment in reducing your upgrade time. The more experienced developers can read directly the newer patterns, like [Surrogate Key][anchor3], [Easy Update][anchor4], [Totals on Subpages][anchor5], [Using Queries instead of nested loops][anchor6] etc.
|
||||
|
||||
**Become a NAV Design Pattern author**
|
||||
|
||||
You have a pattern that you have used successfully? You have ideas on new patterns? You've found some existing design patterns which are used in the product but nobody (except a few) knows how it works, but you find it worth it to explain it for the NAV C/AL developers out there?
|
||||
|
||||
Send your pattern idea to [Bogdana Botez][anchor7] as a private message on the community webpage. Once your first pattern is ready, we will review it as a team, and when signed off, you get author permissions on the Wiki site and from then on, you can continue publishing and editing existing patterns. However, only publish on the Wiki materials that we had signed-off (we don't have moderation capabilities yet, so we count on each author to only make meaningful/agreed changes).
|
||||
|
||||
You and your company also get credit by being mentioned on the pattern and also on our patterns authors page.
|
||||
|
||||
Once you have the idea, writing it down shouldn't take long. You will be helped by adopting [the template ][anchor8]that we've used.
|
||||
|
||||
**Remember the rules**
|
||||
|
||||
When handling design patterns, content quality is critical. We are trying our best to only publish content that is correct, relevant and has been reviewed by multiple developers. Therefore, we review and sign-off all patterns before publishing them (except for the videos made prior to 2015). All text content found on this Wiki and on the NAV Team Blog has been through one, usually multiple iterations of review. If you find something to correct, please comment on the pattern or contact [Bogdana Botez][anchor9], and we will review and update it.
|
||||
|
||||
We are working on creating a set of rules, which would help keeping the content clean and the project on the correct track. [Find the rules here][anchor10].
|
||||
|
||||
|
||||
|
||||
[anchor0]: /nav/w/designpatterns/74.no-series.aspx "No. Series"
|
||||
[anchor1]: /nav/w/designpatterns/76.single-record-setup-table.aspx "Setup Table"
|
||||
[anchor2]: /nav/w/designpatterns/117.hooks-pattern.aspx "Hooks"
|
||||
[anchor3]: /nav/w/designpatterns/122.implementation-of-surrogate-keys-using-autoincrement-pattern.aspx
|
||||
[anchor4]: /nav/w/designpatterns/104.easy-update-of-setup-or-supplementary-information.aspx
|
||||
[anchor5]: /nav/w/designpatterns/155.totals-and-discounts-on-subpages-sales-and-purchases.aspx
|
||||
[anchor6]: /nav/w/designpatterns/123.using-cal-query-objects-instead-of-nested-loops.aspx
|
||||
[anchor7]: /members/bogdana-botez/default.aspx
|
||||
[anchor8]: /nav/w/designpatterns/107.template-for-writing-nav-design-patterns.aspx
|
||||
[anchor9]: /members/bogdana-botez/default.aspx "Bogdana Botez"
|
||||
[anchor10]: /nav/w/designpatterns/239.rules-of-conduct.aspx "Find the rules here"
|
||||
BIN
content/NAVPatterns/4-masked-text/Logo-_2D00_-Masked-Text.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 25 KiB |
109
content/NAVPatterns/4-masked-text/index.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
+++
|
||||
title = "4-masked-text.md"
|
||||
weight = 180
|
||||
+++
|
||||
## Masked Text
|
||||
|
||||
_By Bogdana Botez at Microsoft Development Center Copenhagen_
|
||||
|
||||
_[![ ][image0]][anchor0]
|
||||
_
|
||||
|
||||
__
|
||||
|
||||
**Aliases:** Masking out
|
||||
|
||||
**Context**: In the user interface (UI) of a software system, the user enters protected information such as a password, an access key, a credit card number etc.
|
||||
|
||||
**Problem**: The entered information is visible during data entry and whenever any user (the one who entered the data, or a foreign user) opens the UI.
|
||||
|
||||
****
|
||||
|
||||
**Forces:**
|
||||
|
||||
* **Information disclosure:** sensitive data is visible in the UI.
|
||||
|
||||
**Solution:** Use the "Masked" field property to display dots instead of characters on the sensitive text field in the UI.
|
||||
|
||||
**Usage: **Figure 1 shows how an unmasked and a masked field look in Dynamics NAV. On page Microsoft Dynamics CRM Connection Setup, the first two fields (Dynamics CRM URL and User Name) are not masked. The next field (Password) is masked. As the user types text into the Password field, the characters are one by one replaced with dots. When the user had finished typing and had left the Password field (moved focus to another page element), then a pre-defined number of dots is showed in the field, no matter what the real length of the password is. This is done so that the length of the text is not disclosed. Hence, no matter if the text had 5, 10 or 20 characters, as soon as the user leaves the masked field, 10 dots will be visible.
|
||||
|
||||
[![ ][image1]][anchor1]
|
||||
|
||||
_Figure 1 - The field "Password" is masked._
|
||||
|
||||
To apply this pattern in Dynamics NAV, the developer has two choices:
|
||||
|
||||
**1\. ****Mask everywhere**
|
||||
|
||||
Mask the field in all pages which expose it or will expose it in the future. In this case, masking needs to be set at the table level, by opening the table in design mode and setting the field's property ExtendedDatatype=Masked.
|
||||
|
||||
**2\. ****Mask only in selected pages**
|
||||
|
||||
Mask the field in only a subset of pages. Open the pages where the field should be masked in design mode, open the property page for the field in question, and set ExtendedDatatype=Masked. This option can be used for example when a field should be hidden from most users (in most usual pages), but still visible to administrators in specific pages.
|
||||
|
||||
**Benefits:**
|
||||
|
||||
* **Information protection**: sensitive data is stored and can be used by the system, but once entered it is not visible in the UI anymore.
|
||||
|
||||
**Drawbacks:**
|
||||
|
||||
* **"Forgotten password":** the text in a masked field is hidden from all users, including users who theoretically have the right to see it. In this case, if the developer wishes to disclose the text only to certain users, they have to write extra code (like a lookup trigger) which will verify the user's permissions and if permitted, show or send the clear-text value to the user who requested it.
|
||||
|
||||
**References**
|
||||
|
||||
\[1\]
|
||||
|
||||
"Wikipedia," \[Online\]. Available: https://en.wikipedia.org/wiki/Separation\_of\_concerns.
|
||||
|
||||
\[2\]
|
||||
|
||||
"GUID Structure," \[Online\]. Available: https://msdn.microsoft.com/en-us/library/aa373931(VS.85).aspx.
|
||||
|
||||
\[3\]
|
||||
|
||||
"Wikipedia," \[Online\]. Available: https://en.wikipedia.org/wiki/Binary\_large\_object.
|
||||
|
||||
\[4\]
|
||||
|
||||
E. Wauters, "How Do I: Manage Companies in Microsoft Dynamics NAV 2013 R2".
|
||||
|
||||
\[5\]
|
||||
|
||||
Microsoft, "Multitenant Deployment Architecture," Microsoft, \[Online\]. Available: https://msdn.microsoft.com/en-us/library/dn271675(v=nav.90).aspx.
|
||||
|
||||
\[6\]
|
||||
|
||||
B. Botez, "Setup Table design pattern," Microsoft, 2013\. \[Online\]. Available: https://community.dynamics.com/nav/w/designpatterns/76.setup-table. \[Accessed 31 07 2016\].
|
||||
|
||||
\[7\]
|
||||
|
||||
"Shotgun Surgery," \[Online\]. Available: https://en.wikipedia.org/wiki/Shotgun\_surgery. \[Accessed 31 07 2016\].
|
||||
|
||||
\[8\]
|
||||
|
||||
M. Fowler, Refactoring: Improving the design of existing code, Addison Wesley, 1999\.
|
||||
|
||||
\[9\]
|
||||
|
||||
"Masking out," \[Online\]. Available: https://en.wikipedia.org/wiki/Data\_masking\#Masking\_out. \[Accessed 29 7 2016\].
|
||||
|
||||
\[10\]
|
||||
|
||||
"Key Vault," Microsoft, \[Online\]. Available: https://azure.microsoft.com/en-us/services/key-vault/.
|
||||
|
||||
\[11\]
|
||||
|
||||
"How to: Configure SSL to Secure the Connection to Microsoft Dynamics NAV Web Client," Microsoft, \[Online\]. Available: https://msdn.microsoft.com/en-us/library/hh167264(v=nav.90).aspx. \[Accessed 2 8 2016\].
|
||||
|
||||
\[12\]
|
||||
|
||||
"sniffer," \[Online\]. Available: http://compnetworking.about.com/od/networksecurityprivacy/g/bldef\_sniffer.htm. \[Accessed 02 08 2016\].
|
||||
|
||||
|
||||
|
||||
[anchor0]: Logo-_2D00_-Masked-Text.png
|
||||
[anchor1]: Masking-_2D00_-CRM-Connection-Setup-page.PNG
|
||||
|
||||
|
||||
[image0]: Logo-_2D00_-Masked-Text.png
|
||||
[image1]: Masking-_2D00_-CRM-Connection-Setup-page.PNG
|
||||
BIN
content/NAVPatterns/5-ssl-in-nav/Logo-_2D00_-SSL.JPG
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
content/NAVPatterns/5-ssl-in-nav/SSL-_2D00_-before-and-after.PNG
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
106
content/NAVPatterns/5-ssl-in-nav/index.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
+++
|
||||
title = "5-ssl-in-nav.md"
|
||||
weight = 190
|
||||
+++
|
||||
## SSL in NAV
|
||||
|
||||
_By Bogdana Botez at Microsoft Development Center Copenhagen_
|
||||
|
||||
_[![ ][image0]][anchor0]
|
||||
_
|
||||
|
||||
__
|
||||
|
||||
**Context**: The security of data transmission is just as important as the security of data storage. When data is transmitted over the web, Secure Sockets Layer (SSL) is available to be used with the web client in Dynamics NAV. Microsoft's NAV cloud solution has SSL enabled by default. However, if a partner company chooses to deploy their own NAV, then they need to handle SSL explicitly.
|
||||
|
||||
**Problem**: Although data is stored securely, before it even gets to be stored, it needs to travel the web on a client-server connection, where it is vulnerable.
|
||||
|
||||
**Forces:**
|
||||
|
||||
* **Insecure communication: **When the user enters a password, if unprotected, a network sniffer \[12\] could catch and read it. A sniffer is either a software program or hardware device which examine network traffic. Years ago, sniffers were tools used exclusively by professional network engineers, but nowadays, they are also popular with Internet hackers and people just curious about networking.****A****public Wi-Fi network could easily be eavesdropped by an unwanted actor.
|
||||
|
||||
By using data storage patterns like **Sensitive Data Encapsulation**, **Encryption, Single Point of Access** or **Azure Key Vault**, the sensitive data is preserved securely in the implementation of Dynamics NAV. But before it gets into a secure store, this data needs to be transmitted from the user, through a user interface, on a client-server connection and all the way to the database. Is the data safe while being transmitted?
|
||||
|
||||
**Solution:** To protect the data before it reaches the server, remember to configure SSL (Secure Sockets Layer) in Dynamics NAV.
|
||||
|
||||
_SSL_ is a web protocol that encrypts data that is transmitted over a network to make the data and the network more secure and reliable. A website that is enabled with SSL uses Hypertext Transfer Protocol Secure (HTTPS) instead of Hypertext Transfer Protocol (HTTP) as a communication protocol.
|
||||
|
||||
Figure 1 shows data communication between the client (where the user enters data) and the server (which connects further to the database). Without encryption (left side), data is available in clear text over the wire. A person equipped with a network sniffer can easily intercept and read it. On the right side of the picture, SSL is used to encrypt the user's data. Sniffing the traffic would capture the encrypted stream, but access to the real content would be impeded by encryption.
|
||||
|
||||
**[![ ][image1]][anchor1]**
|
||||
|
||||
_Figure 1 - Data transmission before (http://...) and after SSL encryption (https://...)._
|
||||
|
||||
_
|
||||
_
|
||||
|
||||
**Usage**: the latest information about how to configure SSL for the web client in Dynamics NAV is found online at on MSDN at [https://msdn.microsoft.com/en-us/library/hh167264(v=nav.90).aspx][anchor2].
|
||||
|
||||
**Benefits:**
|
||||
|
||||
* **Secure communication**: encryption protects the data transmitted over a network, so the data is safe all the way on its journey from the user to the database.
|
||||
|
||||
**Consequences:**
|
||||
|
||||
* **Awareness:** a NAV system administrator might not be aware that they need to enable SSL when using the web client.
|
||||
* **Extra work:** there is extra effort to enable SSL on a self-administered NAV system. The good news is that Microsoft's cloud NAV solution has SSL by default and no extra work is required from the developer on that aspect.
|
||||
|
||||
**References**
|
||||
|
||||
\[1\]
|
||||
|
||||
"Wikipedia," \[Online\]. Available: https://en.wikipedia.org/wiki/Separation\_of\_concerns.
|
||||
|
||||
\[2\]
|
||||
|
||||
"GUID Structure," \[Online\]. Available: https://msdn.microsoft.com/en-us/library/aa373931(VS.85).aspx.
|
||||
|
||||
\[3\]
|
||||
|
||||
"Wikipedia," \[Online\]. Available: https://en.wikipedia.org/wiki/Binary\_large\_object.
|
||||
|
||||
\[4\]
|
||||
|
||||
E. Wauters, "How Do I: Manage Companies in Microsoft Dynamics NAV 2013 R2".
|
||||
|
||||
\[5\]
|
||||
|
||||
Microsoft, "Multitenant Deployment Architecture," Microsoft, \[Online\]. Available: https://msdn.microsoft.com/en-us/library/dn271675(v=nav.90).aspx.
|
||||
|
||||
\[6\]
|
||||
|
||||
B. Botez, "Setup Table design pattern," Microsoft, 2013\. \[Online\]. Available: https://community.dynamics.com/nav/w/designpatterns/76.setup-table. \[Accessed 31 07 2016\].
|
||||
|
||||
\[7\]
|
||||
|
||||
"Shotgun Surgery," \[Online\]. Available: https://en.wikipedia.org/wiki/Shotgun\_surgery. \[Accessed 31 07 2016\].
|
||||
|
||||
\[8\]
|
||||
|
||||
M. Fowler, Refactoring: Improving the design of existing code, Addison Wesley, 1999\.
|
||||
|
||||
\[9\]
|
||||
|
||||
"Masking out," \[Online\]. Available: https://en.wikipedia.org/wiki/Data\_masking\#Masking\_out. \[Accessed 29 7 2016\].
|
||||
|
||||
\[10\]
|
||||
|
||||
"Key Vault," Microsoft, \[Online\]. Available: https://azure.microsoft.com/en-us/services/key-vault/.
|
||||
|
||||
\[11\]
|
||||
|
||||
"How to: Configure SSL to Secure the Connection to Microsoft Dynamics NAV Web Client," Microsoft, \[Online\]. Available: https://msdn.microsoft.com/en-us/library/hh167264(v=nav.90).aspx. \[Accessed 2 8 2016\].
|
||||
|
||||
\[12\]
|
||||
|
||||
"sniffer," \[Online\]. Available: http://compnetworking.about.com/od/networksecurityprivacy/g/bldef\_sniffer.htm. \[Accessed 02 08 2016\].
|
||||
|
||||
|
||||
|
||||
[anchor0]: Logo-_2D00_-SSL.JPG
|
||||
[anchor1]: SSL-_2D00_-before-and-after.PNG
|
||||
[anchor2]: https://msdn.microsoft.com/en-us/library/hh167264(v=nav.90).aspx
|
||||
|
||||
|
||||
[image0]: Logo-_2D00_-SSL.JPG
|
||||
[image1]: SSL-_2D00_-before-and-after.PNG
|
||||
17
content/NAVPatterns/actions-images/index.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
+++
|
||||
title = "actions-images.md"
|
||||
weight = 200
|
||||
+++
|
||||
All actions must have an image assigned to them.
|
||||
Bad code
|
||||
|
||||
{ 7 ;1 ;Action ;
|
||||
CaptionML=ENU=Customer - &Balance;
|
||||
RunObject=Report 121 }
|
||||
|
||||
Good code
|
||||
|
||||
{ 7 ;1 ;Action ;
|
||||
CaptionML=ENU=Customer - &Balance;
|
||||
RunObject=Report 121 }
|
||||
Image=Report }
|
||||
BIN
content/NAVPatterns/activity-log/Activity-Log-NAV.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
content/NAVPatterns/activity-log/Activity-Log.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
112
content/NAVPatterns/activity-log/index.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
+++
|
||||
title = "activity-log.md"
|
||||
weight = 210
|
||||
+++
|
||||
## _by Ciprian Iordache at Microsoft Development Center Copenhagen_
|
||||
|
||||
## Activity Log
|
||||
|
||||
**Abstract**
|
||||
|
||||
The Activity Log pattern tracks execution of activities. This is a Dynamics NAV specific implementation of the [Audit Log][anchor0] pattern.
|
||||
|
||||
[![ ][image0]][anchor1]
|
||||
|
||||
**Problem**
|
||||
|
||||
In general, integrating with external systems can be very challenging, due to the complexity of the situation -- connectivity issues, asynchronous operations, user errors, etc. These challenges require sometimes re-trying several times, polling the external system, re-send/re-get data as all these activities can succeed but can very well fail.
|
||||
|
||||
Similar challenges exist in situations where a lengthy, complex task, composed of different steps is to be executed by various people in various timeframes. In case of errors (but sometimes also in case of success) there will be a need to track these activities to see what happened and the actual person which did a specific step.
|
||||
|
||||
In all these cases, we need to be able to troubleshoot.
|
||||
|
||||
A tracking/logging functionality could be implemented for each activity/step separately, but this would lead to code duplication and problems in maintaining the code in future.
|
||||
|
||||
In NAV there is already the Change Log functionality which can record all the data changes that have been done to specific tables, specific fields. However, this functionality is not available for activities. Also, there are few places where separate logging/tracking implementations were done but the current pattern proposes an unified, central way of data recording and enables the user to track all/most of the activities.
|
||||
|
||||
**Solution**
|
||||
|
||||
The Activity Log pattern tracks specific outcome of the activities, in order to be able to assess what went wrong/fine or who performed a specific activity.
|
||||
|
||||
Activity Log pattern
|
||||
|
||||
* records the activity and its outcome (error or success messages)
|
||||
* assembles all messages in one central view and presents them to the user filtered for the specific activity and ordered in reverse chronological order.
|
||||
|
||||
Figure below illustrates the how the Activity Log manifests in the UI. The figure shows a part of an activity log for a posted document that was sent to the document exchange service and illustrates both successful and failed activities.
|
||||
|
||||
[![ ][image1]][anchor2]
|
||||
|
||||
This functionality is implemented in the following way - Activity Log table (TAB710) contains a simple function that allows you to log the result of a task or activity:
|
||||
|
||||
ActivityLog.LogActivity(ContextRecordID,ActivityLog.Status::Failed,ContextDescription,ActivityDescription,ActivityMessage);
|
||||
|
||||
Similar to TAB700 for Error Messaging, the Activity Log table contains a RECORDID that is a link to the parent/context entity. That permits the Activity Log to be used in a generic way, for any kind of entities (tables) and it also permits filtering the data to a specific related entity only before being presenting to the user.
|
||||
|
||||
The following parameters should be provided to the function:
|
||||
|
||||
* RecordID: The record/context for which the activity is logged
|
||||
* Status: The task/activity outcome
|
||||
* Descriptions/Messages: fields that will clearly describe the state and outcome of the task
|
||||
|
||||
To show the log, add a page action, with the caption including the name "<prefix\> Log" and link it to the image named "Log":
|
||||
|
||||
{ ;1 ;Action ;
|
||||
Name=ActivityLog;
|
||||
CaptionML=ENU=Activity Log;
|
||||
ToolTipML=ENU=View the status and any errors if the document was sent as an electronic document or OCR file through the document exchange service.;
|
||||
ApplicationArea=\#Basic,\#Suite;
|
||||
Image=Log;
|
||||
OnAction=VAR
|
||||
ActivityLog@1000 : Record 710;
|
||||
BEGIN
|
||||
ActivityLog.ShowEntries(RECORDID);
|
||||
END;
|
||||
}
|
||||
|
||||
**NAV usages**
|
||||
|
||||
In Dynamics NAV 2016, there is a new feature for sending documents in electronic format to a document exchange service. In this case, sending documents requires multiple steps as it is an asynchronous activity and as such, in order to keep track of what's happening and when the Activity Log functionality was used. That offers later the possibility to see who sent and when a document was sent, when it was dispatched, if any dispatch errors and how many tries have been made until the document was finally dispatched or rejected.
|
||||
|
||||
So as usages in NAV 2016, we have the document exchange and OCR features plus the related posted documents involved in the document exchange feature.
|
||||
|
||||
* COD1294.TXT
|
||||
* COD1410.TXT
|
||||
* PAG1270.TXT
|
||||
* PAG1275.TXT
|
||||
* PAG143.TXT
|
||||
* PAG144.TXT
|
||||
* PAG189.TXT
|
||||
* TAB112.TXT
|
||||
* TAB114.TXT
|
||||
* TAB130.TXT
|
||||
|
||||
**Ideas for improvement**
|
||||
|
||||
Replace the scattered similar functionality (as mentioned above, we have several places having close functionality or similar requirements) with this new pattern.
|
||||
|
||||
**Consequences**
|
||||
|
||||
* Use with caution, similar to the Change Log functionality, as if the pattern will be used extensively in all the activities/operations within NAV, the table might become large containing many records and might cause some performance issues when presenting the data to the client (filtering on the specific activity).
|
||||
* Do not log private or confidential information (passwords, amounts, salaries, sensitive data), unless you are ok with this data to be showed to all users (even to users which normally would not have access to this data), thus overriding the permission sets.
|
||||
* Log only essential information (quality over quantity). Can the logged data be used to analyze the problem, or is it just junk data?
|
||||
|
||||
**NAV Versions**
|
||||
|
||||
Supported from NAV 2016
|
||||
|
||||
**Related Topics**
|
||||
|
||||
Error Message Processing -- provides a similar view and uses similar concepts: has a generic implementation (uses as link the same RECORDID feature) and uses same filtering functionality when displaying the data to the user.
|
||||
|
||||
Audit Log -- as mentioned in the beginning, this pattern is a NAV specific implementation of the audit log pattern.
|
||||
|
||||
|
||||
|
||||
[anchor0]: http://martinfowler.com/eaaDev/AuditLog.html
|
||||
[anchor1]: Activity-Log.jpg
|
||||
[anchor2]: Activity-Log-NAV.jpg
|
||||
|
||||
|
||||
[image0]: Activity-Log.jpg
|
||||
[image1]: Activity-Log-NAV.jpg
|
||||
BIN
content/NAVPatterns/argument-table/0218.Argument-Table-image.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
122
content/NAVPatterns/argument-table/index.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
+++
|
||||
title = "argument-table.md"
|
||||
weight = 220
|
||||
+++
|
||||
# Argument table pattern
|
||||
|
||||
_By Nikola Kukrika and Waldo_
|
||||
|
||||
# Abstract
|
||||
|
||||
The Argument Table pattern is used to provide an extension point for adding new arguments without changing the signature. By grouping multiple arguments into a table the code becomes more readable (function signature and the usage of the function).
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
# Problem
|
||||
|
||||
In CAL overloading function signature is not supported. It is also not possible to provide default values for the function arguments.
|
||||
|
||||
When an argument needs to be added to the function, the existing function needs to be extracted to a new method with an additional argument and the original function will call new method. This will cause an upgrade problem in the future, since the entire body of the method is replaced.
|
||||
|
||||
Second commonly occurring problem is option duplication. In order to pass options often they are duplicated in the signature.
|
||||
|
||||
The last problem that can be solved is high number of arguments. Functions with a high number of arguments are hard to understand. Having arguments grouped within the table with a meaningful name will improve readability and make code easier to understanding.
|
||||
|
||||
Few examples of the bad implementations are as illustrated here:
|
||||
|
||||
## Bad example 1
|
||||
|
||||
|
||||
**PROCEDURE FillInVATReturnData@1200001(VAR DeclarationID@1200000 : Code\[20\];VAR LineID@1200001 :**
|
||||
**Code\[20\];VAR PeerID@1200002 : Code\[20\]; VAR DocumentNo@1200003 : Code\[20\]; VAR NumberOfCopies@1200007 :**
|
||||
**Integer; VAR Uploaded@1200004 : Boolean; VAR Correction@1200005 : Boolean; VAR HasValidationErr@1200006 :**
|
||||
**Boolean);**
|
||||
|
||||
Call
|
||||
|
||||
**FillInVATReturnData(NoSeries, NextLineID, CustomerID, DocumentNo, SingleCopy, ???, ??, ...., ...)**
|
||||
|
||||
In this example the code is hard to read and understand. Adding an additional argument will require refactoring of the existing function. Each time a new argument is added a new function will be created.
|
||||
|
||||
## Bad example 2
|
||||
|
||||
**LOCAL PROCEDURE GetTableSyncSetupW1@3(**
|
||||
**OldTableId@1002 : Integer;**
|
||||
**VAR UpgradeTableId@1001 : Integer;**
|
||||
**VAR TableUpgradeMode@1000 : 'Check, Copy, Move, Force') : Boolean;**
|
||||
BEGIN
|
||||
CASE OldTableId OF
|
||||
DATABASE::"Sales Header":
|
||||
SetTableSyncSetup(0,TableUpgradeMode::Check,UpgradeTableId,TableUpgradeMode);
|
||||
DATABASE::"Posting Exch. Column Def":
|
||||
SetTableSyncSetup(104025,TableUpgradeMode::Copy,UpgradeTableId,TableUpgradeMode);
|
||||
DATABASE::"Payment Export Data":
|
||||
SetTableSyncSetup(0,TableUpgradeMode::Force,UpgradeTableId,TableUpgradeMode);
|
||||
ELSE
|
||||
EXIT(FALSE);
|
||||
END;
|
||||
EXIT(TRUE);
|
||||
END;
|
||||
|
||||
In this example each time a new argument is added all function calls will have to be updated. Option is duplicated in the signature, which will cause issues if a new option is defined or the existing options are renamed.
|
||||
|
||||
# Solution
|
||||
|
||||
By grouping the arguments within the table it is possible to add additional argument and reuse it where it is needed without changing the signature.
|
||||
|
||||
Multiple parameters are grouped within the single object with a meaningful name so the code becomes more readable.
|
||||
|
||||
It is possible to assign default values and to have the code validation.
|
||||
|
||||
Argument table should preferably be a temporary table since the implementation is simpler.
|
||||
|
||||
The examples of usages addressing problems shown above are:
|
||||
|
||||
## Good example 1
|
||||
|
||||
New table
|
||||
TAB 50003 VAT Return Data
|
||||
**PROCEDURE FillInVATReturnData@1200001(VAR VATReturnData@1200000 : Record 50003);**
|
||||
|
||||
****
|
||||
|
||||
VATReturnData.INIT;
|
||||
VATReturnData.NumberOfCopies := GetDefaultNumberOfCopies;
|
||||
VATReturnData.Uploaded := FALSE;
|
||||
|
||||
FillInVATReturnData(VATReturnData);
|
||||
|
||||
By introducing an argument table, code is much more readable since there is a single argument for a function. It is easy to see which arguments are passed in and which are modified in a function.
|
||||
|
||||
## Good example 2
|
||||
|
||||
Good example
|
||||
**PROCEDURE GetTableSyncSetupW1@3(VAR TableSynchSetup@1000 : Record 2000000135);**
|
||||
BEGIN
|
||||
SetTableSyncSetup(DATABASE::"Sales Header",0,TableSynchSetup.Mode::Check);
|
||||
SetTableSyncSetup(DATABASE::"Posting Exch. Column Def",104025,TableSynchSetup.Mode::Copy);
|
||||
SetTableSyncSetup(DATABASE::"Payment Export Data",0,TableSynchSetup.Mode::Force);
|
||||
END;
|
||||
|
||||
Option definition is not encapsulated within the table. Arguments are grouped and we can add additional arguments without the need to change the signature.
|
||||
|
||||
# Downsides
|
||||
|
||||
You need to create one more table
|
||||
|
||||
Complex types can't be embedded as fields in tables (cannot have a record field type etc).
|
||||
|
||||
# NAV Usages
|
||||
|
||||
Upgrade Codeunits
|
||||
|
||||
# Related Patterns
|
||||
|
||||
Posting Routine, Select behavior: Setting fields on existing records in order not to change the signatures.
|
||||
|
||||
|
||||
|
||||
[anchor0]: 0218.Argument-Table-image.png
|
||||
|
||||
|
||||
[image0]: 0218.Argument-Table-image.png
|
||||
16
content/NAVPatterns/begin-as-an-afterword/index.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
+++
|
||||
title = "begin-as-an-afterword.md"
|
||||
weight = 230
|
||||
+++
|
||||
When BEGIN follows THEN, ELSE, DO, it should be on the same line, preceded by one space character. Bad code
|
||||
|
||||
IF ICPartnerRefType = ICPartnerRefType::"Common Item No." THEN
|
||||
BEGIN
|
||||
...
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
IF ICPartnerRefType = ICPartnerRefType::"Common Item No." THEN BEGIN
|
||||
...
|
||||
END;
|
||||
54
content/NAVPatterns/begin-end/index.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
+++
|
||||
title = "begin-end.md"
|
||||
weight = 240
|
||||
+++
|
||||
Only use BEGIN..END to enclose compound statements.
|
||||
Bad code
|
||||
|
||||
IF FINDSET THEN BEGIN
|
||||
REPEAT
|
||||
...
|
||||
UNTIL NEXT = 0;
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
IF FINDSET THEN
|
||||
REPEAT
|
||||
...
|
||||
UNTIL NEXT = 0;
|
||||
|
||||
Bad code
|
||||
|
||||
IF IsAssemblyOutputLine THEN BEGIN
|
||||
TESTFIELD("Order Line No.",0);
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
IF IsAssemblyOutputLine THEN
|
||||
TESTFIELD("Order Line No.",0);
|
||||
|
||||
Bad code
|
||||
|
||||
IF FINDSET THEN
|
||||
REPEAT
|
||||
BEGIN
|
||||
...
|
||||
END;
|
||||
UNTIL NEXT = 0;
|
||||
|
||||
Good code
|
||||
|
||||
IF FINDSET THEN
|
||||
REPEAT
|
||||
...
|
||||
UNTIL NEXT = 0;
|
||||
|
||||
Exception
|
||||
|
||||
// Except for this case
|
||||
IF X THEN BEGIN
|
||||
IF Y THEN
|
||||
DO SOMETHING;
|
||||
END ELSE (not X)
|
||||
15
content/NAVPatterns/binary-operator-line-start/index.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
+++
|
||||
title = "binary-operator-line-start.md"
|
||||
weight = 250
|
||||
+++
|
||||
Do not start a line with a binary operator. Bad code
|
||||
|
||||
"Quantity to Ship" :=
|
||||
Quantity
|
||||
- "Quantity Shipped"
|
||||
|
||||
Good code
|
||||
|
||||
"Quantity to Ship" :=
|
||||
Quantity -
|
||||
"Quantity Shipped"
|
||||
31
content/NAVPatterns/blank-lines/index.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
+++
|
||||
title = "blank-lines.md"
|
||||
weight = 260
|
||||
+++
|
||||
Do not use blank lines at the beginning or end of any functions, after BEGIN, before END, or inside multiline expressions. Bad code
|
||||
|
||||
PROCEDURE MATRIX\_OnDrillDown@1133(MATRIX\_ColumnOrdinal : Integer);
|
||||
BEGIN
|
||||
SetupDrillDownCol(MATRIX\_ColumnOrdinal);
|
||||
DrillDown(FALSE,ValueType);
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
PROCEDURE MATRIX\_OnDrillDown@1133(MATRIX\_ColumnOrdinal : Integer);
|
||||
BEGIN
|
||||
SetupDrillDownCol(MATRIX\_ColumnOrdinal);
|
||||
DrillDown(FALSE,ValueType);
|
||||
END;
|
||||
|
||||
Bad code
|
||||
|
||||
IF NameIsValid AND
|
||||
Name2IsValid
|
||||
THEN
|
||||
|
||||
Good code
|
||||
|
||||
IF NameIsValid AND
|
||||
Name2IsValid
|
||||
THEN
|
||||
BIN
content/NAVPatterns/blocked-entity/2260.BlockedEntityPattern.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 107 KiB |
104
content/NAVPatterns/blocked-entity/index.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
+++
|
||||
title = "blocked-entity.md"
|
||||
weight = 270
|
||||
+++
|
||||
_by Abhishek Ghosh at Microsoft Development Center Copenhagen_
|
||||
|
||||
## Abstract
|
||||
|
||||
The Blocked Entity is used when it is required to stop transactions for an entity (mostly master data), temporarily or permanently.
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
## Description
|
||||
|
||||
To block entities through metadata, read this pattern. To do the same thing through data, read [Data Driven Blocked Entity pattern][anchor1].
|
||||
|
||||
The business entity holds a state that controls if a given transaction is allowed. The state is used by the logic controlling transactions.****The change of state could either be temporary or permanent.
|
||||
|
||||
An example of a temporary halt is when a retail chain selling items has received lot of complaints about an item, and the company wants to stop all transactions, both purchase and sale, with that item until the dealer has clarified the issue with his supplier and possibly received a replacement for the defective stock. Another common example is during counting the physical inventory using cycle counting where the counting is done in one section of a warehouse at a time, so that the regular operations can continue in the other parts of the warehouse. In these situations, it is necessary to block all transactions, such as picks and put-aways, for a bin while warehouse counting is in progress for that bin.
|
||||
|
||||
In contrast, a permanent halt to transactions could be required when an item has become obsolete (or is about to become obsolete), and the company wants to stop further purchase or sale of the item. However, the company wants to maintain the transaction history of the item and, therefore, does not want to delete the item record.
|
||||
|
||||
A simple design implementation of such requirements in Microsoft Dynamics NAV is to add a Blocked field in the entity table (and on the associated page). The implementation takes this state into the logic and checks for the value of this field in related transactions. For most simple scenarios, it is sufficient to have two states on the Blocked field, specifying whether it is allowed to perform transactions for the entity or not.
|
||||
|
||||
In certain situations, however, there could be different levels of blocking. For example, the company could block all sales to a customer that has overdue payments, and the company does not want to allow transactions with this customer until the payments are received. In other situations, the customer may have raised objections about an invoice, and the company has decided not to generate new invoices for the customer until the issue has been resolved. However, the company does want to continue shipping goods to the customer so as not to impact the customer's operations. In these scenarios, it may be necessary to have multiple states on the Blocked field depending on the level of restriction that is needed.
|
||||
|
||||
## Usage
|
||||
|
||||
As mentioned in the previous section, there are two implementations depending on business requirements: The 2-state Boolean field for simple implementations and the multi-state option field for more complex requirements. The implementation flow is similar for both patterns, except how the validation is implemented. The following discusses the two scenarios one by one.
|
||||
|
||||
### Boolean Implementation
|
||||
|
||||
Add a Boolean field named Blocked in the table.
|
||||
|
||||
In the relevant logic, add a condition to check the status of the Blocked flag. The cheapest way is to use a TESTFIELD:
|
||||
|
||||
_<rec variable\>.TESTFIELD(Blocked,FALSE);_
|
||||
|
||||
Alternatively, you can throw a custom error message. However, you should only do that if the default error message thrown by TESTFIELD is not sufficient.
|
||||
|
||||
### Option-Field Implementation
|
||||
|
||||
Add an option field named Blocked in the table. The option values will reflect the different blocked states required by the company.
|
||||
|
||||
Add this field on the card page (or on the List page if the entity does not have a card). As with the Boolean implementation, the convention is to add this field in the right-hand column in the General FastTab of the card page.
|
||||
|
||||
Implement a function in the table that takes the transaction context as input and evaluates the Blocked field to decide whether the transaction should be allowed or not. Optionally, the function can be responsible for notifying the user and bubble up an error message straight away.
|
||||
|
||||
Note: the option field assumes that only one of the multiple options can be active at a time. In other words, the options should be mutually exclusive.
|
||||
|
||||
How not to use the option field in this case: if we want to block an item from sale and/or purchase, the 4 combined options would be **Block none** | **Block Sales** | **Block Purchases** | **Block Sales and Purchases**. This doesn't scale, because if now we need to block another transaction, the number of option would grow too fast. In this situations, it is better to use two Boolean fields: **Blocked Sale**: **true|false** and **Blocked Purchase: true|false**.
|
||||
|
||||
A good example of usage would be for varying the behavior depending on the chosen option, for example by displaying a different error message depending on the reason an Item is blocked. In this case we can have the item **Not Blocked** | **Blocked due to defect** | **Blocked waiting for approval**, etc.
|
||||
|
||||
## NAV Specific Example
|
||||
|
||||
### Boolean Implementation
|
||||
|
||||
[![ ][image1]][anchor2]
|
||||
|
||||
An example of the Boolean implementation on the Item card.
|
||||
|
||||
In codeunit 22 -- Item Jnl.-Post Line, the following lines of code have implemented a check based on the value of the Blocked field:
|
||||
|
||||
IF NOT CalledFromAdjustment THEN
|
||||
Item.TESTFIELD(Blocked,FALSE);
|
||||
|
||||
### Option-Field Implementation
|
||||
|
||||
[![ ][image2]][anchor3]
|
||||
|
||||
An example of the option field implementation on the Customer card.
|
||||
|
||||
The CheckBlockedCustOnDocs and CheckBlockedCustOnJnls functions in the Customer table are responsible for validating the Blocked state with respect to the input document type. These functions are invoked in several areas, such as posting routines, where a status check on the Blocked field is required. This is a good practice where the Blocked implementation gets more complex, as this encourages reuse and ensures uniformity of implementation.
|
||||
|
||||
## NAV Usages
|
||||
|
||||
Entities where the Blocked Entity has been implemented include:
|
||||
|
||||
* Item
|
||||
* G/L Account
|
||||
* Customer
|
||||
* Vendor
|
||||
* Bin
|
||||
|
||||
## Related Topics
|
||||
|
||||
The [Released Entity][anchor4].
|
||||
|
||||
[watch?v=O2R fTSup1o&list=PLhZ3P LY7CqmVszuvtJLujFyHpsVN0Uw&index=16][anchor5]
|
||||
|
||||
|
||||
|
||||
[anchor0]: 2260.BlockedEntityPattern.png
|
||||
[anchor1]: /nav/w/designpatterns/247.data-driven-blocked-entity/edit
|
||||
[anchor2]: 8637.BlockedEntityPattern_5F00_5F00_5F00_Boolean.png
|
||||
[anchor3]: 3056.BlockedEntityPattern_5F00_5F00_5F00_Option.png
|
||||
[anchor4]: /nav/w/designpatterns/115.released-entity.aspx
|
||||
[anchor5]: https://www.youtube.com/watch?v=O2R-fTSup1o&list=PLhZ3P-LY7CqmVszuvtJLujFyHpsVN0U_w&index=16
|
||||
|
||||
|
||||
[image0]: 2260.BlockedEntityPattern.png
|
||||
[image1]: 8637.BlockedEntityPattern_5F00_5F00_5F00_Boolean.png
|
||||
[image2]: 3056.BlockedEntityPattern_5F00_5F00_5F00_Option.png
|
||||
25
content/NAVPatterns/by-reference-parameters/index.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
+++
|
||||
title = "by-reference-parameters.md"
|
||||
weight = 280
|
||||
+++
|
||||
Do not declare parameters by reference if their values are not intended to be changed.
|
||||
|
||||
Unintentional value changes might propagate. Also, it might lead people to believe that value changes are intended.
|
||||
|
||||
Bad code
|
||||
|
||||
LOCAL PROCEDURE ShowMessage@15(VAR Text@1000 : Text\[250\]);
|
||||
BEGIN
|
||||
Text := GetMessageText;
|
||||
IF (Text <\> '') AND GenJnlLineInserted THEN
|
||||
MESSAGE(Text);
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
LOCAL PROCEDURE ShowMessage@15(Text@1000 : Text\[250\]);
|
||||
BEGIN
|
||||
Text := GetMessageText;
|
||||
IF (Text <\> '') AND GenJnlLineInserted THEN
|
||||
MESSAGE(Text);
|
||||
END;
|
||||
|
After Width: | Height: | Size: 30 KiB |
110
content/NAVPatterns/cached-web-service-calls/index.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
+++
|
||||
title = "cached-web-service-calls.md"
|
||||
weight = 290
|
||||
+++
|
||||
_by Mostafa Balat, Microsoft Development Center Copenhagen_
|
||||
|
||||
## Abstract
|
||||
|
||||
In a service-oriented deployment, web services are used to extend NAV's functionality and reach. Depending on how volatile this data is and the corresponding usage scheme, it is expected to be up-to-date within a pre-defined period of time (e.g. once a day).
|
||||
|
||||
## Description
|
||||
|
||||
When NAV is integrated with external services, then the user scenarios become dependent on the data and functions offered by such services. Eventually, there are different approaches through which the external data can be retrieved, stored and used.
|
||||
|
||||
* Dynamic: either exposed by the external service itself or by a separate catalog that NAV can query.
|
||||
* Advantage: data is always up-to-date
|
||||
* Disadvantage: it requires constant connection to the data source.
|
||||
* Static: hard-coded in the database for the user to benefit from.
|
||||
* Advantage: data is promptly available when needed.
|
||||
* Disadvantage: if data changes at some point, it will require a maintenance effort, which exposes the business process to a risk of failure.
|
||||
* Cached: offered through an external service and gets pulled according to a pre-defined refresh rate or manually.
|
||||
* Advantage: data is 'up-to-date' within the rules acceptable by the business process, without extra load on the network resources or the external service.
|
||||
* Disadvantage: if data changes while the auto-refresh did not happen yet, the user may not have access to the latest data; however, the user can manually force a refresh of the data, if asked to do so.
|
||||
|
||||
### When to Use It
|
||||
|
||||
Offer data in lookups that were cached from an external service.
|
||||
|
||||
### Diagram
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
## Usage
|
||||
|
||||
Set up an NAV feature to consume the data from an external service. Refresh the data on a pre-defined refresh rate (e.g. once a day) or when enforced by a power user or an admin. Cache the data in a table and offer it in lookups, as applicable.
|
||||
|
||||
## NAV Specific Example
|
||||
|
||||
### Overview
|
||||
|
||||
**PAG1259 Bank Name - Data Conv. List** offers the required functionality to refresh and display the list of bank names needed to specify which file format to convert to. The page can be accessed from **PAG1260 Bank Data Conv. Service Setup** to display all available bank names. It is also used as a lookup on **PAG370 Bank Account Card**, where it offers a filtered view of the cached bank names based on the Country/Region Code field.
|
||||
|
||||
If **PAG1259 Bank Name - Data Conv. List** is being open and the cached data is 'old', it refreshes the cache. The cached data is stored in **TAB1259 Bank Data Conv. Bank**. Meanwhile, the user has the chance to refresh the data using
|
||||
|
||||
### Code Sample
|
||||
|
||||
OnInit=BEGIN
|
||||
ShortTimeout := 5000;
|
||||
LongTimeout := 30000;
|
||||
END;
|
||||
OnOpenPage=VAR
|
||||
BankDataConvBank@1002 : Record 1259;
|
||||
ImpBankListExtDataHndl@1000 : Codeunit 1289;
|
||||
CountryRegionCode@1004 : Text;
|
||||
HideErrors@1003 : Boolean;
|
||||
BEGIN
|
||||
CountryRegionCode := IdentifyCountryRegionCode(Rec,GETFILTER("Country/Region Code"));
|
||||
IF BankDataConvBank.ISEMPTY THEN BEGIN
|
||||
ImpBankListExtDataHndl.GetBankListFromConversionService(HideErrors,CountryRegionCode,ShortTimeout);
|
||||
EXIT;
|
||||
END;
|
||||
RefreshBankNamesOlderThanToday(CountryRegionCode,HideErrors,ShortTimeout);
|
||||
END;
|
||||
OnAction=VAR
|
||||
ImpBankListExtDataHndl@1000 : Codeunit 1289;
|
||||
FilterNotUsed@1001 : Text;
|
||||
ShowErrors@1003 : Boolean;
|
||||
BEGIN
|
||||
ShowErrors := TRUE;
|
||||
ImpBankListExtDataHndl.GetBankListFromConversionService(ShowErrors,FilterNotUsed,LongTimeout);
|
||||
END;
|
||||
LOCAL PROCEDURE IdentifyCountryRegionCode@1(VAR BankDataConvBank@1002 : Record 1259;Filter@1000 : Text) : Text;
|
||||
VAR
|
||||
CompanyInformation@1001 : Record 79;
|
||||
BlankFilter@1003 : Text;
|
||||
BEGIN
|
||||
BlankFilter := '''''';
|
||||
IF Filter = BlankFilter THEN BEGIN
|
||||
CompanyInformation.GET;
|
||||
BankDataConvBank.SETFILTER("Country/Region Code",CompanyInformation."Country/Region Code");
|
||||
EXIT(BankDataConvBank.GETFILTER("Country/Region Code"));
|
||||
END;
|
||||
EXIT(Filter);
|
||||
END;
|
||||
LOCAL PROCEDURE RefreshBankNamesOlderThanToday@5(CountryRegionCode@1000 : Text;ShowErrors@1002 : Boolean;Timeout@1004 : Integer);
|
||||
VAR
|
||||
BankDataConvBank@1001 : Record 1259;
|
||||
ImpBankListExtDataHndl@1003 : Codeunit 1289;
|
||||
BEGIN
|
||||
IF CountryRegionCode <\> '' THEN
|
||||
BankDataConvBank.SETFILTER("Country/Region Code",CountryRegionCode);
|
||||
BankDataConvBank.SETFILTER("Last Update Date",'<%1',TODAY);
|
||||
IF BankDataConvBank.FINDFIRST THEN
|
||||
ImpBankListExtDataHndl.GetBankListFromConversionService(ShowErrors,CountryRegionCode,Timeout);
|
||||
END;
|
||||
|
||||
## NAV Usages
|
||||
|
||||
Bank name lookup on the Bank Account card for dynamically identifying the format to use to generate a bank-specific payment file.
|
||||
|
||||
## Ideas for Improvement
|
||||
|
||||
Expose the refresh rate through a setup table to make it easily configurable without changing the code.
|
||||
|
||||
|
||||
|
||||
[anchor0]: Cached_5F00_Web_5F00_Service_5F00_Calls_5F00_Diagram.png
|
||||
|
||||
|
||||
[image0]: Cached_5F00_Web_5F00_Service_5F00_Calls_5F00_Diagram.png
|
||||
26
content/NAVPatterns/captionml-for-system-tables/index.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
+++
|
||||
title = "captionml-for-system-tables.md"
|
||||
weight = 300
|
||||
+++
|
||||
CaptionML should always be specified on a page field for a system table. By default, system tables do not have captions, so if you need to use them in the UI then captions need to be added.
|
||||
|
||||
Bad code
|
||||
|
||||
...
|
||||
{ 2 ;2 ;Field ;
|
||||
SourceExpr=Name }
|
||||
...
|
||||
OBJECT Table 2000000000 User
|
||||
...
|
||||
{ 2 ; ;Name ;Text50 }
|
||||
|
||||
Good code
|
||||
|
||||
...
|
||||
{ 2 ;2 ;Field ;
|
||||
CaptionML=ENU=Name;
|
||||
SourceExpr=Name }
|
||||
...
|
||||
OBJECT Table 2000000000 User
|
||||
...
|
||||
{ 2 ; ;Name ;Text50 }
|
||||
17
content/NAVPatterns/case-actions/index.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
+++
|
||||
title = "case-actions.md"
|
||||
weight = 310
|
||||
+++
|
||||
A CASE action should start on a line after the possibility. Bad code
|
||||
|
||||
CASE Letter OF
|
||||
'A': Letter2 := '10';
|
||||
'B': Letter2 := '11';
|
||||
|
||||
Good code
|
||||
|
||||
CASE Letter OF
|
||||
'A':
|
||||
Letter2 := '10';
|
||||
'B':
|
||||
Letter2 := '11';
|
||||
18
content/NAVPatterns/class-coupling/index.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
+++
|
||||
title = "class-coupling.md"
|
||||
weight = 320
|
||||
+++
|
||||
Do not write functions that have high class coupling. This makes the code hard to maintain.
|
||||
|
||||
Bad code
|
||||
|
||||
Any procedure / trigger that has class coupling of \> 30
|
||||
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
Any procedure / trigger that has class coupling of <= 30\.
|
||||
Class coupling is computed by summing the unique instances of the following in a code block:
|
||||
- every unique usage of a complex C/AL data type (table, codeunit, etc) as 1\.
|
||||
- every unique usage of a DotNet type as 1\.
|
||||
12
content/NAVPatterns/code-of-conduct/index.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
+++
|
||||
title = "code-of-conduct.md"
|
||||
weight = 330
|
||||
+++
|
||||
Find below the rules to be used when disseminating or relating to the NAV Design Patterns.
|
||||
|
||||
1. Only use materials published in text on the NAV Design Patterns Wiki site. If you received from us, any unpublished materials, please observe that they are subject to change and have not been approved for external use.
|
||||
2. When referencing a NAV Design Pattern, you must remember to also reference its author and company where the author is employed. You will find the author and his/her company at the beginning of each pattern, under the title.
|
||||
3. When referencing a NAV Design Patterns project, you must make it clear that this is a community project, driven by Microsoft, with multiple developers involved from both Microsoft and the community.
|
||||
4. When using published text content of NAV Design Patterns, do not alter the text in any way that was published on the Wiki site, unless is has been reviewed by the patterns team and signed off by someone at Microsoft in writing.
|
||||
5. If you have other materials which have not received explicit signoff in writing from me, where I have specifically stated that they are valid design patterns ready for publishing, please do not name them "NAV Design Patterns" (or anything similar). You are free to use your own content, but do not associate it in any way with NAV Design Patterns unless it is signed off in writing.
|
||||
6. If you do choose to use your own content, you must make it clear that it is not a NAV Design Pattern.
|
||||
15
content/NAVPatterns/colon-usage-in-case/index.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
+++
|
||||
title = "colon-usage-in-case.md"
|
||||
weight = 340
|
||||
+++
|
||||
The last possibility on a CASE statement must be immediately followed by a colon. Bad code
|
||||
|
||||
CASE DimOption OF
|
||||
DimOption::"Global Dimension 1" :
|
||||
DimValue."Dimension Code" := GLSetup."Global Dimension 1 Code";
|
||||
|
||||
Good code
|
||||
|
||||
CASE DimOption OF
|
||||
DimOption::"Global Dimension 1":
|
||||
DimValue."Dimension Code" := GLSetup."Global Dimension 1 Code";
|
||||
39
content/NAVPatterns/comments-curly-brackets/index.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
+++
|
||||
title = "comments-curly-brackets.md"
|
||||
weight = 350
|
||||
+++
|
||||
Never use curly bracket comments. During development, the "Block comment" functionality can be used instead. However, in production code, block comments are not recommended. Bad code
|
||||
|
||||
PeriodTxt: {Period}
|
||||
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
PeriodTxt: // Period
|
||||
|
||||
|
||||
|
||||
Bad code
|
||||
|
||||
PROCEDURE MATRIX\_OnAfterGetRecord@10(MATRIX\_ColumnOrdinal : Integer);
|
||||
BEGIN
|
||||
{
|
||||
IF ShowColumnName THEN
|
||||
MatrixHeader := MatrixRecords\[MATRIX\_ColumnOrdinal\].Name
|
||||
ELSE
|
||||
MatrixHeader := MatrixRecords\[MATRIX\_ColumnOrdinal\].Code;
|
||||
}
|
||||
MatrixRecord := MatrixRecords\[MATRIX\_ColumnOrdinal\];
|
||||
AnalysisValue := CalcAmt(ValueType,TRUE);
|
||||
MATRIX\_CellData\[MATRIX\_ColumnOrdinal\] := AnalysisValue;
|
||||
END;
|
||||
|
||||
Good code
|
||||
|
||||
PROCEDURE MATRIX\_OnAfterGetRecord@10(MATRIX\_ColumnOrdinal : Integer);
|
||||
BEGIN
|
||||
MatrixRecord := MatrixRecords\[MATRIX\_ColumnOrdinal\];
|
||||
AnalysisValue := CalcAmt(ValueType,TRUE);
|
||||
MATRIX\_CellData\[MATRIX\_ColumnOrdinal\] := AnalysisValue;
|
||||
END;
|
||||
14
content/NAVPatterns/comments-spacing/index.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
+++
|
||||
title = "comments-spacing.md"
|
||||
weight = 360
|
||||
+++
|
||||
Always start comments with // followed by one space character.
|
||||
Bad code
|
||||
|
||||
RowNo += 1000; //Move way below the budget
|
||||
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
RowNo += 1000; // Move way below the budget
|
||||
|
After Width: | Height: | Size: 31 KiB |
39
content/NAVPatterns/conditional-cascading-update/index.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
+++
|
||||
title = "conditional-cascading-update.md"
|
||||
weight = 370
|
||||
+++
|
||||
_by Jan Hoek at IDYN _
|
||||
|
||||
## **Abstract**
|
||||
|
||||
The Conditional Cascading Update pattern is used to intelligently populate fields whose values depend on other field values. In this pattern description, the field triggering the update will be called "source field", and the depending field will be called "target field".
|
||||
|
||||
## **Description
|
||||
**
|
||||
|
||||
The value of one table field sometimes depends on the value of another field, typically following an application-defined transformation (note that we're talking about transformations of field values here. This has nothing to do with e.g. form transformation), such as conversion to uppercase, removal of certain characters etc.
|
||||
|
||||
If the target field is non-editable, said transformation is usually the only way for the target field to receive new values, so no irreproducible information can be lost. However, if the target field is editable, the user may have cared enough to override the default (transformed) value, in which case revalidating the source field should not blindly replace the target field's value.
|
||||
|
||||
## **Usage**
|
||||
|
||||
In the OnValidate trigger of the source field, test if the target field value is either blank, or equal to the transformed value of the source field's previous contents. If it is, populate the target field's value with the transformed source field value. If it is not, do nothing (effectively preserving the value set by the user).
|
||||
|
||||
## **NAV Specific Example**
|
||||
|
||||
In the base application, this pattern can be found in Search Name/Search Description fields, which are updated with the uppercase value from the corresponding Name/Description field when the latter is validated, only if the Search Name/Description in question is currently blank, or equal to the (uppercase equivalent) of the previous contents of the Name/Description field.
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
In this particular case, the transformation between source and target fields is implicit and due to the different data types of the fields (text vs. code). Note how the field triggers of the Search Name field itself do not contain any logic linked to this pattern.
|
||||
|
||||
## **Consequences**
|
||||
|
||||
There is a case when this pattern should not be used. If the target field is non-editable, this pattern will not add any value, since there won't be any user-overridden values to protect.
|
||||
|
||||
|
||||
|
||||
[anchor0]: 3124.T18_5F00_Name_5F00_OnValidate.png
|
||||
|
||||
|
||||
[image0]: 3124.T18_5F00_Name_5F00_OnValidate.png
|
||||
16
content/NAVPatterns/confirm/index.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
+++
|
||||
title = "confirm.md"
|
||||
weight = 380
|
||||
+++
|
||||
Always end CONFIRM with a question mark.
|
||||
Bad code
|
||||
|
||||
ChangeAllOpenedEntriesQst@1000 : TextConst 'ENU=Do you want to change all open entries for every customer and vendor that are not blocked';
|
||||
...
|
||||
IF CONFIRM(ChangeAllOpenedEntriesQst,TRUE) THEN
|
||||
|
||||
Good code
|
||||
|
||||
ChangeAllOpenedEntriesQst@1000 : TextConst 'ENU=Do you want to change all open entries for every customer and vendor that are not blocked?';
|
||||
...
|
||||
IF CONFIRM(ChangeAllOpenedEntriesQst,TRUE) THEN
|
||||
BIN
content/NAVPatterns/copy-document/clip_5F00_image006.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
content/NAVPatterns/copy-document/clip_5F00_image008.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
content/NAVPatterns/copy-document/clip_5F00_image010.jpg
Normal file
|
After Width: | Height: | Size: 44 KiB |
118
content/NAVPatterns/copy-document/index.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
+++
|
||||
title = "copy-document.md"
|
||||
weight = 390
|
||||
+++
|
||||
_By Bogdan Sturzoiu at Microsoft Development Center Copenhagen_
|
||||
|
||||
## Abstract
|
||||
|
||||
The goal of the Copy Document pattern is to create a replica of an existing open or closed document (posted or not posted), by moving the lines and, optionally, the header information from the source document to a destination document.
|
||||
|
||||
## Description
|
||||
|
||||
Documents are widely used by most of our customers. Many times, a significant portion of these documents are similar to each other, either by sharing the same customer, vendor, type, or line structure. Being able to re-use a document as a base for creating a new one is therefore an important means of saving time.
|
||||
|
||||
Other business scenarios require that a newly created document is applied to an existing document. For example, in returns management, a return order can be the reversal of an existing order and can therefore be copied from the original order. Other times, there is even a legal requirement to match the document to its source. For example, credit memos need to be applied to the originating Invoice.
|
||||
|
||||
For these reasons, NAV supports the copying of documents as a method to re-use or link documents.
|
||||
|
||||
The Copy Document functionality is used in the following situations:
|
||||
|
||||
* The user wants to create a new open sales document (Quote, Order, Blanket Order, Invoice, Return Order, Credit Memo) based on an existing posted or non-posted sales document (Quote, Blanket Order, Order, Invoice, Return Order, Credit Memo, Posted Shipment, Posted Invoice, Posted Return Receipt, Posted Credit Memo).
|
||||
* The user wants to create a new open purchase document (Quote, Order, Blanket Order, Invoice, Return Order, Credit Memo) based on an existing posted or non-posted purchase document (Quote, Blanket Order, Order, Invoice, Return Order, Credit Memo, Posted Shipment, Posted Invoice, Posted Return Receipt, Posted Credit Memo).
|
||||
* The user wants to create a new production order (Simulated, Planned, Firm Planned or Released) based on an existing production order (Simulated, Planned, Firm Planned, Released or Finished).
|
||||
* The user wants to create a new assembly order based on an existing assembly document (Quote, Blanket Order, Order and Posted Order).
|
||||
* The user wants to create a new service contract or quote based on an existing service contract or quote.
|
||||
* The user wants to create all relevant return-related documents. For example, from a sales return order, the user can recreate the involved supply chain documentation, by copying the information upwards to a purchase return order (if the items need to be returned to the vendor), purchase order (if the items need to be reordered), and sales order (if the items need to be re-sent to the customer).
|
||||
|
||||
**Note**
|
||||
|
||||
* Not all to and from combinations are allowed. For example, you can only copy to open document types, since the posted documents are not editable.
|
||||
* The destination document needs to have the header fully created. For example, a Sales Order will need to have the Sell-To Customer No. populated.
|
||||
|
||||
## Usage
|
||||
|
||||
The Dynamics NAV application developer can take into account using the Copy Document design pattern when they have requirements such as:
|
||||
|
||||
* To provide a quick and efficient way of moving content from a document to another.
|
||||
* To allow reusing the document history as a template for new documents.
|
||||
* To allow linking of documents that need to be applied to each other.
|
||||
|
||||
The Copy Document pattern involves the following entities:
|
||||
|
||||
1. Source document tables for document header and line. For example,Sales Header/Line.
|
||||
2. Destination document tables for document header and line.
|
||||
|
||||
**Note:**The source document header/line and destination document header/line tables do not need to be the same. For example, you can copy a Sales Shipment Header/Lines into a Sales Header/Lines.
|
||||
3. Copy Document engine: COD6620, Copy Document Mgt.
|
||||
4. Copy Document report for a specific document type. The report requires the following parameters:
|
||||
* Source Document Type
|
||||
* Source Document No.
|
||||
* Include Header (optional)
|
||||
* Recalculate Lines (optional)
|
||||
Example: REP901, Copy Assembly Document
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
## Usage Sequence
|
||||
|
||||
**Precondition**: The user creates a new destination document Header, filling up the required information.
|
||||
|
||||
**Step 1**: The user runs the Copy Document report (element no. 4), filling up the parameters:
|
||||
|
||||
* Source Document Type
|
||||
* Source Document No.
|
||||
* Include Header and/or Recalculate Lines (not all Copy Document reports have these).
|
||||
|
||||
**Step 2**: The report copies the information in the source tables (Header and Line) into the destination tables (Header and Line).
|
||||
|
||||
**Post processing**: The user performs additional editing of the destination document.
|
||||
|
||||
The sequence flow of the pattern is described in the following diagram.
|
||||
|
||||
[![ ][image1]][anchor1]
|
||||
|
||||
Example: Copy Sales Document for Credit Memos.
|
||||
|
||||
In the standard version of Microsoft Dynamics NAV, the Copy Document functionality is implemented in the Sales Credit Memo window as shown in the following section.
|
||||
|
||||
\*\*\*
|
||||
|
||||
**Precondition**: The user enters data in PAGE44, Sales Credit Memo.
|
||||
|
||||
[![ ][image2]][anchor2]
|
||||
|
||||
**Step 1**: The user runs REP292, Copy Sales Document from the Sales Credit Memo window, populating the required parameters. The Include Header and Recalculate Lines fields are selected.
|
||||
|
||||
[![ ][image3]][anchor3]
|
||||
|
||||
**Step 2**: The Sales Credit Memo window is populated with information from the source sales document.
|
||||
|
||||
[![ ][image4]][anchor4]
|
||||
|
||||
**Post processing**: The user can now do additional editing of the sales credit memo.
|
||||
|
||||
# NAV Implementations
|
||||
|
||||
1. Copy Sales Document (REP292)
|
||||
2. Copy Purchase Document (REP492)
|
||||
3. Copy Service Document (REP5979)
|
||||
4. Copy Assembly Document (REP901)
|
||||
|
||||
[watch?v=aTiwroXwW0&list=PLhZ3P LY7CqmVszuvtJLujFyHpsVN0Uw&index=17][anchor5]
|
||||
|
||||
|
||||
|
||||
[anchor0]: /cfs-file/__key/communityserver-wikis-components-files/00-00-00-00-42/clip_5F00_image002.gif
|
||||
[anchor1]: /cfs-file/__key/communityserver-wikis-components-files/00-00-00-00-42/clip_5F00_image004.gif
|
||||
[anchor2]: clip_5F00_image006.jpg
|
||||
[anchor3]: clip_5F00_image008.jpg
|
||||
[anchor4]: clip_5F00_image010.jpg
|
||||
[anchor5]: https://www.youtube.com/watch?v=aTiwroXwW_0&list=PLhZ3P-LY7CqmVszuvtJLujFyHpsVN0U_w&index=17
|
||||
|
||||
|
||||
[image0]: /resized-image/__size/750x0/__key/communityserver-wikis-components-files/00-00-00-00-42/clip_5F00_image002.gif
|
||||
[image1]: /resized-image/__size/750x0/__key/communityserver-wikis-components-files/00-00-00-00-42/clip_5F00_image004.gif
|
||||
[image2]: clip_5F00_image006.jpg
|
||||
[image3]: clip_5F00_image008.jpg
|
||||
[image4]: clip_5F00_image010.jpg
|
||||
BIN
content/NAVPatterns/create-data-from-templates/2134.Picture6.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
content/NAVPatterns/create-data-from-templates/2816.Picture3.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 52 KiB |
BIN
content/NAVPatterns/create-data-from-templates/4118.Picture1.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
content/NAVPatterns/create-data-from-templates/4341.Picture4.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
content/NAVPatterns/create-data-from-templates/7271.Picture4.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
231
content/NAVPatterns/create-data-from-templates/index.md
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
+++
|
||||
title = "create-data-from-templates.md"
|
||||
weight = 400
|
||||
+++
|
||||
_by Nikola Kukrika at Microsoft Development Center Copenhagen_
|
||||
|
||||
## Abstract
|
||||
|
||||
For many records, such as Items, Customers, and Vendors, users have to enter the same sets of data again and again. This is tedious, error-prone (users forget to enter a field or they choose the wrong group), and difficult to learn for some users.
|
||||
|
||||
We can group sets of data as templates to speed up and simplify the process of entering data in Microsoft Dynamics NAV. For example, the process of creating a new customer could be simplified so that users only have to enter information that is specific for every individual customer, e.g. Name and Address/Contact.
|
||||
|
||||
## Description
|
||||
|
||||
This pattern solves the problem of creating new records based on their type. You should use it whenever there is a large set of data that could be grouped in a meaningful way.
|
||||
|
||||
In Microsoft Dynamics NAV 2013 R2, we have extended the Configuration Templates feature so that templates can be used in different languages than the language they were created in. We have also added the ability to set related templates so that related records can be inserted, such as dimensions for customers, items, and vendors.
|
||||
|
||||
The pattern consists of two parts:
|
||||
|
||||
1\. Using templates to create new records or applying templates to existing records
|
||||
|
||||
2\. Defining and updating existing templates
|
||||
|
||||
## Usage
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
Using the patterns involves three steps.
|
||||
|
||||
1) As a first step, we must insert a record. This can be done either through C/AL code or by letting the user create a record using the **New** action.
|
||||
|
||||
2) After the record is created, we must apply the template. This is done by using the **UpdateRecord** function in the **Config. Template Management **codeunit (8612).
|
||||
|
||||
**Config. Template Lines** records reference one **Config. Template Header** record (lines pattern). The lines can be of type:
|
||||
|
||||
* **Field** - Stores a field value that will be applied to the record
|
||||
* **Related** Template -- References a Config. Template Header record for a related template.
|
||||
|
||||
The **UpdateRecord** function applies values to the record one line at the time. One of the requirements was to be possible to use configuration templates in different language/regional settings than the template was created in.
|
||||
|
||||
To support this scenario, when applying the**Config. Template Line** record, **GLOBALLANGUAGE** is set to the language ID of the field. This is important because the default value is stored as text, so we need to use the same formatting that NAV was running on when the template was created. Otherwise, data types, such as Boolean, Date, etc., will raise validation errors.
|
||||
|
||||
Any updates to a **Config. Template Line** record will automatically update the language ID to the current one. Since lines are applied one by one, it is supported to have lines with different language IDs belonging to the same template.
|
||||
|
||||
3) After we have applied the template to the record, we can insert related templates. For example, when you insert an item, you may want to insert dimensions as well. You must implement the logic to apply or then modify the related templates, since this depends on the logic and the relationship between the records. Lines with **Type = Related Template** are used to reference related templates.
|
||||
|
||||
Code example (Insert a record, apply a template, and insert the related templates):
|
||||
|
||||
**// First insert a record Customer.INSERT(TRUE);**
|
||||
|
||||
**// Apply a template RecRef.GETTABLE(Customer);**
|
||||
|
||||
ConfigTemplateMgt.UpdateRecord(ConfigTemplateHeader,RecRef);
|
||||
|
||||
RecRef.SETTABLE(Customer);
|
||||
|
||||
**// Insert Dimensions -- related templates**
|
||||
|
||||
MiniDimensionsTemplate.InsertDimensionsFromTemplates(ConfigTemplateHeader,Customer."No.",DATABASE::Customer);
|
||||
|
||||
Code to insert related templates (dimensions):
|
||||
|
||||
**FUNCTION InsertDimensionsFromTemplates(ConfigTemplateHeader : Record "Config. Template Header";MasterRecordNo : Code\[20\];TableID : Integer)**
|
||||
|
||||
**// There are multiple records (multiple dimensions per master record)**
|
||||
|
||||
**// We have to set filter******
|
||||
|
||||
ConfigTemplateLine.SETRANGE(Type,ConfigTemplateLine.Type::"Related Template");****
|
||||
|
||||
ConfigTemplateLine.SETRANGE("Data Template Code",ConfigTemplateHeader.Code);****
|
||||
|
||||
****
|
||||
|
||||
IF ConfigTemplateLine.FINDSET THEN****
|
||||
|
||||
REPEAT****
|
||||
|
||||
ConfigTemplateHeader.GET(ConfigTemplateLine."Template Code");
|
||||
|
||||
**// Ensure that the table where the template belongs to is Dimensions**
|
||||
|
||||
**// We could have other related templates******
|
||||
|
||||
IF ConfigTemplateHeader."Table ID" = DATABASE::"Default Dimension" THEN****
|
||||
|
||||
InsertDimensionFromTemplate(ConfigTemplateHeader,MasterRecordNo,TableID);****
|
||||
|
||||
UNTIL ConfigTemplateLine.NEXT = 0;****
|
||||
|
||||
****
|
||||
|
||||
**// Create a new Dimensions Record and link it to the Master Record******
|
||||
|
||||
**FUNCTION InsertDimensionFromTemplate(ConfigTemplateHeader : Record "Config. Template Header";MasterRecordNo : Code\[20\];TableID : Integer)**
|
||||
|
||||
DefaultDimension.INIT;
|
||||
|
||||
DefaultDimension."No." := MasterRecordNo;****
|
||||
|
||||
DefaultDimension."Table ID" := TableID;****
|
||||
|
||||
DefaultDimension."Dimension Code" := GetDefaultDimensionCode(ConfigTemplateHeader);****
|
||||
|
||||
DefaultDimension.INSERT;****
|
||||
|
||||
****
|
||||
|
||||
RecRef.GETTABLE(DefaultDimension);****
|
||||
|
||||
ConfigTemplateMgt.UpdateRecord(ConfigTemplateHeader,RecRef);****
|
||||
|
||||
RecRef.SETTABLE(DefaultDimension);
|
||||
|
||||
**To surface the action in the product, you have three options:**
|
||||
|
||||
1. **Recommended** - Implement a separate action called **New from Template**.
|
||||
2. **Optional** - Implement the apply template function on the document itself. This is especially good in scenarios where users are allowed to change the template.
|
||||
3. **Alternative** - Remove the new action by configuration or set Insert Allowed to FALSE on the list (this will block the creation of new records from the lookup). Implement an application action named **New** and tie it to your code.
|
||||
|
||||
**Note: ** In Microsoft Dynamics C5 2014, we chose to remove the **New** action with configuration since we wanted to promote the functionality and avoid the confusion in having too many options. However this might be difficult to maintain with a larger set of pages.
|
||||
|
||||
**To view or edit templates, you have two options:**
|
||||
|
||||
1. Use the **Config. Template List** table (8620) and the **Config. Template Header Card **table (8618).
|
||||
|
||||
This is a generic solution that is not very usable and is error-prone (no lookups, checks for length, table relation checks, etc.) The default value is a text field of 250 characters, which might be much more than the field length, and may lead to validation errors when used. Users will most likely not be able to use this page.
|
||||
|
||||
[![ ][image1]][anchor1]
|
||||
|
||||
2\. Implement custom pages resembling the document.
|
||||
|
||||
This is optional if you want to enable the users to create and modify templates. In C5 2014, we created temporary tables with the same fields as the main record. Based on this temporary record, we built a page that resembles a document.
|
||||
|
||||
Example of the **Customer Template** page:
|
||||
|
||||
[![ ][image2]][anchor2]
|
||||
|
||||
The goals of this solution were:
|
||||
|
||||
* To make the setup page resemble a document page so that it is easy to use with basic validation and lookups.
|
||||
* To have only one place to store templates and maintain only one business logic for applying them, namely in the **Configuration Template Header** table.
|
||||
* To avoid any lateral effects of doing validation on the temporary master record. Doing validation on fields, even though the record itself is temporary, could permanently modify other data in the database. For example, if you insert a new record in the **Customer** table, even in a temporary table, a Contact record is created, which will not be temporary.
|
||||
* Testability: It is easy to test through RecordRef that the template table matches the main table. We can compare field lengths, data types, table relations, etc. The test is able to detect that they are out of sync, so it is easy to prevent errors.
|
||||
|
||||
One example in the product is the **Mini Customer Template **table (1300).
|
||||
|
||||
The table itself contains very little code. OnModify, OnInsert, and OnDelete triggers update the **Configuration Header** and **Configuration Lines** tables. The following functions in the **Config. Template Management **codeunit (8612), are used for this:
|
||||
|
||||
* ConfigTemplateManagement.CreateConfigTemplateAndLines
|
||||
* ConfigTemplateManagement.UpdateConfigTemplateAndLines
|
||||
* ConfigTemplateManagement.DeleteRelatedTemplates
|
||||
|
||||
The CreateFieldRefArray function is used as an interface function on all the temporary template tables. It builds data to be read/written to the configuration templates.
|
||||
|
||||
To further enhance the usability, we have provided the following additional functionality:
|
||||
|
||||
* Create a template from the existing record: The user opens an existing record and creates a template from that record. All the fields that are defined in the CreateFieldRefArray function are used to create the new template.
|
||||
* Templates list: This page is used by users to select templates or create new ones. Depending on which templates they are working on, we show different template cards.
|
||||
|
||||
## NAV Specific Example
|
||||
|
||||
In C5 2014, this is the workflow:
|
||||
|
||||
The user opens the **Customers List** window and selects **New**
|
||||
|
||||
**[![ ][image3]][anchor3]**
|
||||
|
||||
**[][anchor3]**From this page, the user can view the template, edit it, or create a new one. Selecting a template will populate the customer card and open a new record. From the existing record, the user has options to save as a template or opening a list of templates to maintain available templates. Selecting a template will populate the customer card and open a new record. From the existing record, the user has options to save as a template or opening a list of templates to maintain available templates.
|
||||
|
||||
[![ ][image4]][anchor4]
|
||||
|
||||
From the **Customer Card Template **window, we can invoke the **Dimensions** action, through which we can define the dimensions that will be inserted together with the template:
|
||||
|
||||
[![ ][image5]][anchor5]
|
||||
|
||||
## NAV Usages
|
||||
|
||||
This pattern is used in Microsoft Dynamics C5 2014 in the following objects:
|
||||
|
||||
* Temporary template tables:
|
||||
* **Mini Customer Template **table (1300)
|
||||
* **Mini Item Template **table (1301)
|
||||
* **Mini Dimensions Template** table(1302)
|
||||
* **Mini Vendor Template** table (1303)
|
||||
|
||||
* Pages to define templates:
|
||||
* **Mini Customer Template Card** page (1341)
|
||||
* **Mini Item Template Card **page (,1342)
|
||||
* **Mini Dimensions Template List** page (1343)
|
||||
* **Mini Vendor Template Card** page (1344)
|
||||
|
||||
* Pages that use the templates:
|
||||
* **Mini Customer List** page (1301)
|
||||
* **Mini Item List** page ( 1303)
|
||||
* **Mini Vendor List** page (1331)
|
||||
|
||||
In the standard version of Microsoft Dynamics NAV, we use the **Apply Template** action on the following pages:
|
||||
|
||||
* **Customer Card** page (21)
|
||||
* **Vendor Card** page (26)
|
||||
* **Item Card** page (30)
|
||||
* **Resource Card** page (, 76)
|
||||
* Other similar cards.
|
||||
|
||||
## Ideas for improvement
|
||||
|
||||
Implement the solution in the standard version of Microsoft Dynamics NAV and extend the Apply Template functionality to insert dimensions.
|
||||
|
||||
It is possible that users end up with a large number of templates if they need many different data combinations. An improvement could be to split templates into smaller groups, grouping only part of the fields that are related, and then apply only these.
|
||||
|
||||
[watch?v=F0CTvoyKSmI&list=PLhZ3P LY7CqmVszuvtJLujFyHpsVN0Uw&index=20][anchor6]
|
||||
|
||||
|
||||
|
||||
[anchor0]: 4118.Picture1.png
|
||||
[anchor1]: 2816.Picture3.png
|
||||
[anchor2]: 7271.Picture4.png
|
||||
[anchor3]: 4341.Picture4.png
|
||||
[anchor4]: 3482.Picture-5.png
|
||||
[anchor5]: 2134.Picture6.png
|
||||
[anchor6]: https://www.youtube.com/watch?v=F0CTvoyKSmI&list=PLhZ3P-LY7CqmVszuvtJLujFyHpsVN0U_w&index=20
|
||||
|
||||
|
||||
[image0]: 4118.Picture1.png
|
||||
[image1]: 2816.Picture3.png
|
||||
[image2]: 7271.Picture4.png
|
||||
[image3]: 4341.Picture4.png
|
||||
[image4]: 3482.Picture-5.png
|
||||
[image5]: 2134.Picture6.png
|
||||
BIN
content/NAVPatterns/create-urls-to-nav-clients/1778.url1.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
content/NAVPatterns/create-urls-to-nav-clients/7802.url2.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
298
content/NAVPatterns/create-urls-to-nav-clients/index.md
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
+++
|
||||
title = "create-urls-to-nav-clients.md"
|
||||
weight = 410
|
||||
+++
|
||||
_By Mike Borg Cardona and Bogdana Botez at Microsoft Development Center Copenhagen_
|
||||
|
||||
## Abstract
|
||||
|
||||
This article illustrates NAV platform functionality to be used by C/AL developers.
|
||||
|
||||
The URL builder function, GETURL, is released in Microsoft Dynamics NAV 2013 R2 to reduce coding time for developers who need to create various URL strings to run application objects in either the win client, the web client, or on web services. In addition, the GETURL function makes multitenancy features more transparent to C/AL developers.
|
||||
|
||||
## Description
|
||||
|
||||
Ever had to construct win client URLs like the one below?
|
||||
|
||||
dynamicsnav://myserver:7046/myInstance/myCompany/runpage?page=26
|
||||
|
||||
Today, Microsoft Dynamics NAV also provides a web client. This means that you must update your code to construct web client URLs too. What about multitenancy? The URL Builder should know if it is running in a multitenant setup and it should know how to choose the right tenant. What about maintaining this code?
|
||||
|
||||
The good news is that GETURL has been introduced to handle all URL building for you.
|
||||
|
||||
GETURL automatically handles:
|
||||
|
||||
* Multitenancy
|
||||
* Correct URL format for each client
|
||||
* Publicly accessible hostnames.
|
||||
|
||||
## Usage
|
||||
|
||||
The format is:
|
||||
|
||||
\[String :=\] GETURL(ClientType\[, Company\]\[, Object Type\]\[, Object Id\]\[, Record\])
|
||||
|
||||
Where:
|
||||
|
||||
* **Client Type** can be: Current, Default, Windows, Web, SOAP, or OData. This enables a range of scenarios for the C/AL developer, such as moving to the web client without changing code to decide where the URL should point to. This is done either by setting Client Type to Current, and just ensuring that web is used to invoke the link creation, or by setting Client Type to Default and changing its value to Web when it is ready to move to the web platform.
|
||||
* **Object Type** and **Object ID** define the type of the application object to run (Table, Page, Report, Codeunit, Query, or XMLport) and its ID.
|
||||
* **Record** specifies the actual data to run the URL on, such as:
|
||||
|
||||
Vendor.GET("Account No.");
|
||||
|
||||
GETURL(CLIENTTYPE:WEB,COMPANYNAME, OBJECTTYPE::Page,27,Vendor)
|
||||
|
||||
**Note**: It is currently not possible to set filters on the record that you sent as a last parameter to the GETURL function. However, it is possible to write your own code to compute and append the filter string to the URL that is created by the GETURL function.
|
||||
|
||||
The server name and instance are extracted automatically by GETURL and do not need to be specified by the C/AL developer. Furthermore, the multitenancy setup is transparent to the C/AL developer. No multitenancy parameters are specified when you call GETURL, because the function knows from the server setup if it is running in a multitenant environment and if so, it will add a string like "&tenant=MyTenant" to the URL.
|
||||
|
||||
## When to Use
|
||||
|
||||
The GETURL function can generally be used every time a URL must be created. The following are some scenarios where the function is particularly useful.
|
||||
|
||||
* Document approvals. For more information, see the "NAV Usage Example" section.
|
||||
* Reports containing drill-down links. (Beware of the resource cost of adding a new URL element to the Report dataset.)
|
||||
* When planning to write code for, or migrate to, various display targets (Microsoft Dynamics NAV Windows client, Microsoft Dynamics NAV web client, Microsoft Dynamics NAV web services) without having to explicitly specify which client to use.
|
||||
|
||||
## Examples of Usage
|
||||
|
||||
The following are examples of calls to GETURL and their corresponding return value:
|
||||
|
||||
**Command**
|
||||
|
||||
**URL**
|
||||
|
||||
GETURL(CLIENTTYPE::Win)
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71//
|
||||
|
||||
GETURL(CLIENTTYPE::Web)
|
||||
|
||||
https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient
|
||||
|
||||
GETURL(CLIENTTYPE::OData)
|
||||
|
||||
http://MyServer:7048/DynamicsNAV71/OData
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP)
|
||||
|
||||
http://MyServer:7047/DynamicsNAV71/WS/Services
|
||||
|
||||
GETURL(CLIENTTYPE::Current) ie. When running this code on a Win client session
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71//
|
||||
|
||||
GETURL(CLIENTTYPE::Default) ie. When the Server config key DefaultClient is set to Windows
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71//
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,COMPANYNAME)
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,'')
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71//
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,'NONEXISTING Corp')
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71/NONEXISTING Corp/
|
||||
|
||||
GETURL(CLIENTTYPE::Web,COMPANYNAME)
|
||||
|
||||
https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=CRONUS
|
||||
|
||||
GETURL(CLIENTTYPE::Web,'')
|
||||
|
||||
https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient
|
||||
|
||||
GETURL(CLIENTTYPE::Web,'NONEXISTING Corp')
|
||||
|
||||
https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=NONEXISTING Corp
|
||||
|
||||
GETURL(CLIENTTYPE::OData,COMPANYNAME)
|
||||
|
||||
http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS')
|
||||
|
||||
GETURL(CLIENTTYPE::OData,'')
|
||||
|
||||
http://MyServer:7048/DynamicsNAV71/OData
|
||||
|
||||
GETURL(CLIENTTYPE::OData,'NONEXISTING Corp')
|
||||
|
||||
http://MyServer:7048/DynamicsNAV71/OData/Company('NONEXISTING Corp')
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,COMPANYNAME)
|
||||
|
||||
http://MyServer:7047/DynamicsNAV71/WS/CRONUS/Services
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,'')
|
||||
|
||||
http://MyServer:7047/DynamicsNAV71/WS/Services
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,'NONEXISTING Corp')
|
||||
|
||||
http://MyServer:7047/DynamicsNAV71/WS/NONEXISTING Corp/Services
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Table,27)
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runtable?table=27
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,27)
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=27
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Report,6)
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runreport?report=6
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Codeunit,5065)
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runcodeunit?codeunit=5065
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Query,9150)
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runquery?query=9150
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::XmlPort,5150)
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runxmlport?xmlport=5150
|
||||
|
||||
GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Page,27) ie. When the Web Service is published
|
||||
|
||||
http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS')/PAG27Vendors
|
||||
|
||||
GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Query,9150) ie. When the Web Service is published
|
||||
|
||||
http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS')/QUE9150MyCustomers
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Page,27)Â ie. When the Web Service is published
|
||||
|
||||
http://MyServer:7047/DynamicsNAV71/WS/CRONUS/Page/PAG27Vendors
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Codeunit,5065) ie. When the Web Service is published
|
||||
|
||||
http://MyServer:7047/DynamicsNAV71/WS/CRONUS/Codeunit/COD5065EmailLogging
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,27,record) List Page
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=27&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw
|
||||
|
||||
GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,26,record) Card Page
|
||||
|
||||
dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=26&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw
|
||||
|
||||
GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,27,record) List Page
|
||||
|
||||
https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=CRONUS&page=27&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw
|
||||
|
||||
GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,26,record) Card Page
|
||||
|
||||
https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=CRONUS&page=26&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw
|
||||
|
||||
GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Page,27,record)
|
||||
|
||||
http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS')/PAG27Vendors('IC1030')
|
||||
|
||||
GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,27)
|
||||
|
||||
https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=CRONUS&page=27
|
||||
|
||||
GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Report,6)
|
||||
|
||||
https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=CRONUS&report=6
|
||||
|
||||
If the GETURL function is called with invalid parameters, it will return an empty string. In that case, you can find the related error text by calling the GETLASTERRORTEXT function.
|
||||
|
||||
**Function Call**
|
||||
|
||||
**Error Message**
|
||||
|
||||
GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Table,27)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Codeunit,5065)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Query,9150)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::XmlPort,5150)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Table,27)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Page,27)
|
||||
|
||||
The Page object, 27, that is specified for the GetUrl function has not been published in the Web Services table.
|
||||
|
||||
GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Report,6)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Codeunit,5065)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Query,9150)
|
||||
|
||||
The Query object, 9150, that is specified for the GetUrl function has not been published in the Web Services table.
|
||||
|
||||
GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::XmlPort,5150)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Table,27)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Page,27)
|
||||
|
||||
The Page object, 27, that is specified for the GetUrl function has not been published in the Web Services table.
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Report,6)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Codeunit,5065)
|
||||
|
||||
The Codeunit object, 5065, that is specified for the GetUrl function has not been published in the Web Services table.
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Query,9150)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::XmlPort,5150)
|
||||
|
||||
The specified object type parameter for the GetUrl function is not valid.
|
||||
|
||||
GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Page,27,record)
|
||||
|
||||
You cannot specify a record parameter for the GetUrl function when the object type is SOAP
|
||||
|
||||
## NAV Specific Example
|
||||
|
||||
The following example shows how to use the GETURL function in codeunit 440 to ensure that the notification mail in Document Approvals can link to both the Microsoft Dynamics NAV Windows client and the Microsoft Dynamics NAV web client:
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
This resulting UI looks as follows.
|
||||
|
||||
[![ ][image1]][anchor1]
|
||||
|
||||
The first link opens the approval document in the Microsoft Dynamics NAV Windows client. The second link (Web view) opens the same document in the Microsoft Dynamics NAV web client.
|
||||
|
||||
|
||||
|
||||
[anchor0]: 1778.url1.jpg
|
||||
[anchor1]: 7802.url2.jpg
|
||||
|
||||
|
||||
[image0]: 1778.url1.jpg
|
||||
[image1]: 7802.url2.jpg
|
||||
BIN
content/NAVPatterns/creating-custom-charts/0246.Picture6.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
content/NAVPatterns/creating-custom-charts/1411.Picture2.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
content/NAVPatterns/creating-custom-charts/1541.Picture7.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
content/NAVPatterns/creating-custom-charts/1781.Picture5.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
content/NAVPatterns/creating-custom-charts/1803.Picture7.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
content/NAVPatterns/creating-custom-charts/2553.Picture8.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
content/NAVPatterns/creating-custom-charts/5153.Picture1.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
content/NAVPatterns/creating-custom-charts/5545.Picture9.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
content/NAVPatterns/creating-custom-charts/5582.Picture10.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
205
content/NAVPatterns/creating-custom-charts/index.md
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
+++
|
||||
title = "creating-custom-charts.md"
|
||||
weight = 420
|
||||
+++
|
||||
_by Nikola Kukrika at Microsoft Development Center Copenhagen_
|
||||
|
||||
## Abstract
|
||||
|
||||
The goal of this solution is to enable you to:
|
||||
|
||||
1. Use charts in the web client.
|
||||
2. Create charts with custom functionality.
|
||||
|
||||
## Description
|
||||
|
||||
This pattern enables you to implement a business chart (Specific Chart type) in a way that is maintainable and reusable on other pages. This also enables you to provide specific functionality that is not possible with the Generic Chart type and it enables you to show charts in the web client.
|
||||
|
||||
The Business Chart add-in is a special because it is a combination of .NET and Javascript add-ins depending on the display target. In the web client, it renders a JavaScript control, while in the win client, it renders a .Net control. Because of this behavior, you can expect minor differences in how the chart is presented in the win client versus in the web client. Note that this implementation is specific to NAV platform code, because it is not possible to create add-ins that combines .NET and JavaScript by using a framework API.
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
Example of the same chart in the win client:
|
||||
|
||||
[![ ][image1]][anchor1]
|
||||
|
||||
The most obvious differences in chart rendering in the two clients are: Slightly different line heights, slightly different chart height, legends in web-client charts can be used as toggle filters to show/hide groups (this is not possible in the win client).
|
||||
|
||||
### Implementation Overview
|
||||
|
||||
[![ ][image2]][anchor2]
|
||||
|
||||
### Add-in Buffer Table
|
||||
|
||||
This table is used to encapsulate the logic of the Business Chart Add-in. The table handles the following logic:
|
||||
|
||||
* Storing chart values and conversion from .NET to C/AL and vice versa
|
||||
* Handling of captions: We must use C/AL to provide multilanguage text in add-ins. In addition, the multilanguage text must be encapsulated in a single place, because we pass/read the same dataset from/to the add-in.
|
||||
* DrillDown logic
|
||||
* Other helper data related functions, for displaying date, periods, etc.\[Bogdana1\] \[NK2\] \[NK3\]
|
||||
|
||||
**Note:** It is recommended that you reuse the **Business Chart Buffer **table****(485) as a buffer table or extend. It is a generic table which should cover most of the use cases. Implement a new buffer table only if this table does not meet your needs.
|
||||
|
||||
### CardPart page
|
||||
|
||||
The CardPart page hosts the Business Chart add-in and must use the add-in buffer table as a source table.
|
||||
|
||||
On the page, you must implement the following triggers:
|
||||
|
||||
* **AddInReady** -- Executed when the page is done rendering. Used to initialize the add-in.
|
||||
* **DataPointClicked** -- Single-click on an element on the chart.
|
||||
* **DataPointDoubleClicked** -- Double-click on an element on the chart
|
||||
|
||||
The CardPart usually contains a StatusText variable to provide more information about the chart or dataset and a set of actions to control the chart.
|
||||
|
||||
The most commonly used actions are:
|
||||
|
||||
* **Select Chart**, **Previous Chart**, **Next Chart**
|
||||
* **Set Period**, **Work Date**
|
||||
* Actions to filter the data set
|
||||
* **Refresh**
|
||||
* **Chart Information **-- a tooltip with a description of the chart and how data is calculated.
|
||||
|
||||
### Optional: Preserving User Personalization
|
||||
|
||||
One of the most common functionalities is personalization. If the chart can be customized by the user, you should store the settings that the user has entered and apply them the next time the chart is loaded.
|
||||
|
||||
To do this, you need the following:
|
||||
|
||||
* A setup record to store the data. You can use the **Business Chart User Setup** table (487) or create a new setup table if you need to store more information.
|
||||
* A management codeunit to write/apply the settings to the chart and to encapsulate other logic. Since we should not write code on pages, the code for the actions and other logic that does not apply to the setup record****should go in this codeunit.
|
||||
* Setup pages where users can customize how the chart is shown and set different settings.
|
||||
|
||||
The relation between the components is visualized in the following diagram\[
|
||||
|
||||
[![ ][image3]][anchor3]
|
||||
|
||||
### Optional: Show Multiple Charts within a Single CardPart
|
||||
|
||||
This option is useful on a Role Center where you want to show multiple charts using different datasets within a single part. In that case, you need a record to store the last chart selection, the setup records, and code units for separate charts.
|
||||
|
||||
See, for example, the implementation of the **Mini Generic Chart** page (1390), which uses **MiniChartManagment** CodeUnit to manage separate management codeunits for charts and their setup records. The last selected chart is stored in a separate table, **Mini Chart Definition** (1310).
|
||||
|
||||
[![ ][image4]][anchor4]
|
||||
|
||||
## Usage
|
||||
|
||||
To implement the pattern, create a new ChartPart and set the source table to **Business Chart Buffer**.
|
||||
|
||||
Add a field named **BusinessChart** and set the ControlAddIn property to Microsoft.Dynamics.Nav.Client.BusinessChart.
|
||||
|
||||
Then implement the AddInReady event. This event is executed when the page is done rendering. Code within this method must call the Update method from the **Business Chart Buffer** table, Update(CurrPage.BusinessChart) to initialize the chart and assign initial values.
|
||||
|
||||
If you need a setup record and codeunit, then it is a good idea to encapsulate this logic within a method.
|
||||
|
||||
### NAV Specific Example 1
|
||||
|
||||
Implementation of the **Finance Performance Chart** page (762)
|
||||
|
||||
**BusinessChart::AddInReady()**
|
||||
|
||||
UpdateChart(Period::" ");
|
||||
|
||||
**LOCAL UpdateChart(Period : ',Next,Previous')**
|
||||
|
||||
MoveAndUpdateChart(Period,0);
|
||||
|
||||
**LOCAL MoveAndUpdateChart(Period : ',Next,Previous';Move : Integer)**
|
||||
|
||||
AccSchedChartManagement.GetSetupRecordset(AccountSchedulesChartSetup,AccountSchedulesChartSetup.Name,Move);
|
||||
|
||||
AccSchedChartManagement.UpdateData(Rec,Period,AccountSchedulesChartSetup);
|
||||
|
||||
Update(CurrPage.BusinessChart);
|
||||
|
||||
StatusText := GetCurrentSelectionText("Period Filter Start Date","Period Filter End Date");
|
||||
|
||||
In the MoveAndUpdateChart method, the AccSchedChartManagement codeunit gets a setup record and updates it if necessary. Then, it initializes the chart with setup data and sets the StatusText to show the period for which data is displayed. The same method is used by the actions to move and update the chart so that there is no code duplication.
|
||||
|
||||
The following code is used to implement **DataPointClicked**
|
||||
|
||||
******BusinessChart::DataPointClicked(point : DotNet "Microsoft.Dynamics.Nav.Client.BusinessChart.BusinessChartDataPoint")**
|
||||
|
||||
SetDrillDownIndexes(point);
|
||||
|
||||
AccSchedChartManagement.DrillDown(Rec,AccountSchedulesChartSetup);
|
||||
|
||||
SetDrillDownindexes is a method from the **Business Chart Buffer** table that maps the DotNet point variable to C/AL data, so it must be used. The next method that you must implement is the action to be performed on Drilldown.
|
||||
|
||||
The **DataPointDoubleClicked **trigger has the same implementation logic as the DataPointClicked trigger.
|
||||
|
||||
### NAV Specific Example 2
|
||||
|
||||
Implementation of chart part 1390 on the **Small Business Role Center** page (9022)
|
||||
|
||||
[![ ][image5]][anchor5]
|
||||
|
||||
This chart part contains data from multiple charts within a single part. The **Status Text** field shows the name of the chart and the current period. Users can browse through the charts with **Next Chart **and**Previous Chart** or use **Select Chart** to choose from a list of available charts.
|
||||
|
||||
[![ ][image6]][anchor6]
|
||||
|
||||
On this dialog, users can choose if a chart should be enabled or disabled. If the chart is not enabled, it will be skipped on the **Previous Chart **and **Next Chart** actions. Charts used by this part use different codeunits and setup records. If the user changes the selected chart, this option will be saved and applied next time role center is opened.
|
||||
|
||||
Users can also change the period length.
|
||||
|
||||
[![ ][image7]][anchor7]
|
||||
|
||||
Choosing the **Chart Information** button opens a short description of the chart.
|
||||
|
||||
[![ ][image8]][anchor8]
|
||||
|
||||
## NAV Usages
|
||||
|
||||
Implementation of multiple charts within a single part:
|
||||
|
||||
* Page 1390, **Mini Generic Chart**
|
||||
|
||||
Charts that use a setup record and select the chart with **Customize Chart Setup** pages:
|
||||
|
||||
* Page 772, **Inventory Performance**
|
||||
* Page 771, **Purchase Performance**
|
||||
* Page 770, **Sales Performance**
|
||||
* Page 762, **Finance Performance**
|
||||
|
||||
Chart that uses the **Business Chart User Setup** table:
|
||||
|
||||
* Page 768, **Aged Acc. Receivable Chart**
|
||||
|
||||
Other implementations:
|
||||
|
||||
* Page 972, **Time Sheet Chart**
|
||||
* Page 869, **Cash Flow Chart**
|
||||
* Page 760, **Trailing Sales Orders Chart**
|
||||
|
||||
## Ideas for improvement
|
||||
|
||||
We should consider making a generic table for the last chart that the user has used.
|
||||
|
||||
We should investigate if we could make generic code for selecting periods and other common functionality by using RecordRefs.
|
||||
|
||||
Add-In improvements -- Different ways to visualize the data and to pick colors for categories.
|
||||
|
||||
As a nice-to-have feature, we could implement functionality to cycle through the charts with a timer.
|
||||
|
||||
|
||||
|
||||
[anchor0]: 5153.Picture1.png
|
||||
[anchor1]: 1411.Picture2.png
|
||||
[anchor2]: 1781.Picture5.png
|
||||
[anchor3]: 0246.Picture6.png
|
||||
[anchor4]: 1803.Picture7.png
|
||||
[anchor5]: 1541.Picture7.png
|
||||
[anchor6]: 2553.Picture8.png
|
||||
[anchor7]: 5545.Picture9.png
|
||||
[anchor8]: 5582.Picture10.png
|
||||
|
||||
|
||||
[image0]: 5153.Picture1.png
|
||||
[image1]: 1411.Picture2.png
|
||||
[image2]: 1781.Picture5.png
|
||||
[image3]: 0246.Picture6.png
|
||||
[image4]: 1803.Picture7.png
|
||||
[image5]: 1541.Picture7.png
|
||||
[image6]: 2553.Picture8.png
|
||||
[image7]: 5545.Picture9.png
|
||||
[image8]: 5582.Picture10.png
|
||||
BIN
content/NAVPatterns/cross-session-events/PubSub.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
153
content/NAVPatterns/cross-session-events/index.md
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
+++
|
||||
title = "cross-session-events.md"
|
||||
weight = 430
|
||||
+++
|
||||
#### _By Nikolai L'Estrange, from TVision Technology Ltd. in the UK__
|
||||
_
|
||||
|
||||
### Abstract
|
||||
|
||||
Track things that happen in other NAV Sessions.
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
### Problem
|
||||
|
||||
In Microsoft Dynamics NAV you can fire a function whenever something changes within your session (and from NAV 2016 this is even easier with the new Event model), however there is not an easy way to know what is happening in other sessions. Sometimes you would like to know what has happened since your last read, without reading everything again, e.g. when you need to pass a large dataset to a Control Add-in.
|
||||
|
||||
A common way of handling this with Ledger Tables is to make note of the last record you read, and continuously poll to see if there are any new records. However this is restricted to strictly sequentially entered tables.
|
||||
|
||||
### Solution
|
||||
|
||||
There is a common pattern in many other languages called [Publish-Subscribe][anchor1] (or PubSub) that solves the same issue. We can implement the same pattern in NAV using a Table as a message queue platform and polling this table. We have named this pattern "Cross Session Events" in order to avoid confusion with the standard NAV Events which use the terms Publisher and Subscriber, and to try and describe more accurately when you would need this pattern.
|
||||
|
||||
The pattern has four components:
|
||||
|
||||
* **Publisher(s)**: These push messages to the Message Broker.
|
||||
* **Subscriber Records**: Identifies the Subscriber and store filters to say what messages the Subscriber and interested in receiving.
|
||||
* **Message Broker**: This distributes all messages sent in to all Subscribers that have expressed an interest (i.e. the message is within their filters).
|
||||
* **Message Queue**: To hold the messages for each Subscriber. Generally once these messages are read, they are deleted.
|
||||
|
||||
### Example
|
||||
|
||||
An example of this would be when we have multiple users looking at the same set of data and we want their screens to update in "real time" whenever one of them makes a change, without doing a full refresh. We will use the [Observer pattern][anchor2] to capture the change (act as the Publisher) and then create a Table to hold Subscribers and Filters (Change Observer), a Table to be the Message Queue (Change Notification), and a Codeunit to be the Message Broker and help with the polling (ObserverMgt).
|
||||
|
||||
Below are the table definitions:
|
||||
|
||||
**Change Observer:
|
||||
**"Table ID" Integer "Observable Table"
|
||||
"Server ID" Integer
|
||||
"Session ID" Integer
|
||||
|
||||
**Change Notification:
|
||||
**"Table ID" Integer "Observable Table"
|
||||
"Server ID" Integer
|
||||
"Session ID" Integer
|
||||
"Entry No." Integer AutoIncrement
|
||||
"Type of Change" Option Insert,Modify,Delete,Rename
|
||||
"Record ID" RecordID
|
||||
... (other fields to indicate what has changed)
|
||||
|
||||
|
||||
The Change Observer table identifies the Subscriber using Server ID and Session ID, and then in this example there is only one filter, which is the Table ID we want to listen to any changes. In this case all three fields are in the Primary Key.
|
||||
|
||||
The Change Notification table then has the same three fields plus an Entry No. as its Primary Key, and in this example borrows heavily from the Change Log code to fill in the rest of the message.
|
||||
|
||||
_**Note:** _Other examples of the pattern could have very different fields to identify the Subscriber, Filters and then whatever fields needed for content of the Message.
|
||||
|
||||
Our Message Broker Codeunit will also serve as a central place to create Subscribers (Listen and StopListening functions) and a place to Poll for Messages. Note that the Poll function deletes the Messages as it reads them.
|
||||
|
||||
**Listen(TableID : Integer)**
|
||||
WITH Observer DO BEGIN
|
||||
"Table ID" := TableID;
|
||||
"Server ID" := SERVICEINSTANCEID;
|
||||
"Session ID" := SESSIONID;
|
||||
INSERT(TRUE);
|
||||
COMMIT;
|
||||
END;
|
||||
**StopListening(TableID : Integer)
|
||||
**WITH Observer DO BEGIN
|
||||
RESET;
|
||||
SETRANGE("Server ID",SERVICEINSTANCEID);
|
||||
SETRANGE("Session ID",SESSIONID);
|
||||
SETRANGE("Table ID",TableID);
|
||||
DELETEALL(TRUE);
|
||||
COMMIT;
|
||||
END;
|
||||
**NotifyAll(ChangeNotification : Record "Change Notification")**
|
||||
WITH Observer DO BEGIN
|
||||
RESET;
|
||||
SETRANGE("Table ID",ChangeNotification."Table ID");
|
||||
IF FINDSET THEN REPEAT
|
||||
Notify(Observer,ChangeNotification);
|
||||
UNTIL NEXT = 0;
|
||||
END;
|
||||
**Notify(Observer : Record "Change Observer";ChangeNotification : Record "Change Notification")**
|
||||
WITH ChangeNotification DO BEGIN
|
||||
"Server ID" := Observer."Server ID";
|
||||
"Session ID" := Observer."Session ID";
|
||||
"Entry No." := 0;
|
||||
INSERT;
|
||||
END;
|
||||
**Poll(TableID : Integer;VAR TempChangeNotification : TEMPORARY Record "Change Notification")**
|
||||
WITH ChangeNotification DO BEGIN
|
||||
TempChangeNotification.RESET;
|
||||
TempChangeNotification.DELETEALL;
|
||||
RESET;
|
||||
SETRANGE("Table ID",TableID);
|
||||
SETRANGE("Server ID",SERVICEINSTANCEID);
|
||||
SETRANGE("Session ID",SESSIONID);
|
||||
IF FINDSET THEN REPEAT
|
||||
TempChangeNotification := ChangeNotification;
|
||||
TempChangeNotification.INSERT;
|
||||
MARK(TRUE);
|
||||
UNTIL NEXT = 0;
|
||||
MARKEDONLY(TRUE);
|
||||
DELETEALL;
|
||||
END;
|
||||
|
||||
**__**The final part of this example is an object that calls the functions above. In this example we will use a Page with a PingPong Timer Control to do the polling in (almost) real time. These are the functions on the page:
|
||||
|
||||
**OnQueryClosePage(CloseAction : Action None) : Boolean**
|
||||
ObserverMgt.StopListening(DATABASE::"NAV Whiteboard Booking");
|
||||
**Timer::AddInReady()**
|
||||
IF ObserverMgt.Listen(DATABASE::"NAV Whiteboard Booking") THEN
|
||||
CurrPage.Timer.Ping(1000);
|
||||
**Timer::Pong()**
|
||||
CallUpdate;
|
||||
CurrPage.Timer.Ping(1000);
|
||||
**LOCAL CallUpdate()**
|
||||
ObserverMgt.Poll(DATABASE::"NAV Whiteboard Booking",TempChangeNotification);
|
||||
WITH TempChangeNotification DO BEGIN
|
||||
IF FINDSET THEN REPEAT
|
||||
IF "Type of Change" = "Type of Change"::Delete THEN BEGIN
|
||||
...
|
||||
END ELSE IF RecRef.GET("Record ID") THEN BEGIN
|
||||
...
|
||||
END;
|
||||
UNTIL NEXT = 0;
|
||||
END;
|
||||
|
||||
### Consequences
|
||||
|
||||
The PingPong control is only available on the Windows Client, so if you want to use another client you will need to use another solution to Poll for Messages. Therefore this pattern is not always going to be "real time".
|
||||
|
||||
### Related Topics
|
||||
|
||||
This pattern was originally described in the following blog:
|
||||
|
||||
[https://geeknikolai.wordpress.com/2015/10/30/pubsub-pattern-in-dynamics-nav-2016/][anchor3]
|
||||
|
||||
Below is the Wikipedia link to the PubSub pattern
|
||||
|
||||
[https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe\_pattern][anchor1]
|
||||
|
||||
|
||||
|
||||
[anchor0]: PubSub.png
|
||||
[anchor1]: https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern
|
||||
[anchor2]: /nav/w/designpatterns/248.observer
|
||||
[anchor3]: https://geeknikolai.wordpress.com/2015/10/30/pubsub-pattern-in-dynamics-nav-2016/
|
||||
|
||||
|
||||
[image0]: PubSub.png
|
||||
BIN
content/NAVPatterns/cue-table/Cue-Table-Figure-1.JPG
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
content/NAVPatterns/cue-table/Cue-Table.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
141
content/NAVPatterns/cue-table/index.md
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
+++
|
||||
title = "cue-table.md"
|
||||
weight = 440
|
||||
+++
|
||||
## Cue Table
|
||||
|
||||
_By Bogdana Botez at Microsoft Development Center Copenhagen
|
||||
_
|
||||
|
||||
_[![ ][image0]][anchor0]
|
||||
_
|
||||
|
||||
Cues are the second usual application of the [**Singleton Table**][anchor1] pattern in Dynamics NAV, after [**Setup Tables**][anchor2].
|
||||
|
||||
**Context**: The user gets overview information about the business on the Dynamics NAV Role Center page.
|
||||
|
||||
Figure 1 - Cue information in Dynamics NAV shows cue information seen by the user on the **Sales Order Processor** role center.
|
||||
|
||||
[![ ][image1]][anchor3]
|
||||
|
||||
The overview information consists of summed-up numbers, calculated from business data, like for example how many sales orders are still open, how many shipments are ready to go, or partially shipped, how many documents are waiting for approval etc.
|
||||
|
||||
**Problem**: NAV stores data in tables. By definition, a table is a repetitive structure containing multiple lines, each line having a different piece of the information. But sometimes this repetitive information needs to be summed-up or otherwise synthetized, and presented as an overview.
|
||||
|
||||
**
|
||||
**
|
||||
|
||||
**Solution:** Store overview information in a singleton table.
|
||||
|
||||
****
|
||||
|
||||
There are two ways of calculating overview information in NAV.
|
||||
|
||||
1. By using a FlowField. This applies for simpler calculations, like filtered or unfiltered counts, sums etc.
|
||||
2. By writing C/AL code to perform custom calculations. Use this when:
|
||||
|
||||
* * The way to calculate the overview is too complex for flow fields, or
|
||||
* The data needs to be pulled from an external system (like Dynamics CRM, QuickBooks or any external integration).
|
||||
|
||||
**
|
||||
**
|
||||
|
||||
The implementation of Cues is already described in detail on MSDN, in [Creating and Customizing Cues][anchor4] and in [Walkthrough: Creating a Cue Based on a FlowField][anchor5].
|
||||
|
||||
**NAV Usages**
|
||||
|
||||
Table 1 - Cue tables in Dynamics NAV****shows some examples of singleton tables used for creating Cues.
|
||||
|
||||
Table ID
|
||||
|
||||
Table Name
|
||||
|
||||
1313
|
||||
|
||||
Activities Cue
|
||||
|
||||
5370
|
||||
|
||||
CRM Synch. Job Status Cue
|
||||
|
||||
9042
|
||||
|
||||
Team Member Cue
|
||||
|
||||
9050
|
||||
|
||||
Warehouse Basic Cue
|
||||
|
||||
9051
|
||||
|
||||
Warehouse WMS Cue
|
||||
|
||||
9052
|
||||
|
||||
Service Cue
|
||||
|
||||
9053
|
||||
|
||||
Sales Cue
|
||||
|
||||
9054
|
||||
|
||||
Finance Cue
|
||||
|
||||
9055
|
||||
|
||||
Purchase Cue
|
||||
|
||||
9056
|
||||
|
||||
Manufacturing Cue
|
||||
|
||||
9057
|
||||
|
||||
Job Cue
|
||||
|
||||
9058
|
||||
|
||||
Warehouse Worker WMS Cue
|
||||
|
||||
9059
|
||||
|
||||
Administration Cue
|
||||
|
||||
9060
|
||||
|
||||
SB Owner Cue
|
||||
|
||||
9061
|
||||
|
||||
RapidStart Services Cue
|
||||
|
||||
9063
|
||||
|
||||
Relationship Mgmt. Cue
|
||||
|
||||
9069
|
||||
|
||||
O365 Sales Cue
|
||||
|
||||
9070
|
||||
|
||||
Accounting Services Cue
|
||||
|
||||
Table 1 - Cue tables in Dynamics NAV
|
||||
|
||||
___
|
||||
_
|
||||
|
||||
|
||||
|
||||
[anchor0]: Cue-Table.png
|
||||
[anchor1]: /nav/w/designpatterns/151.singleton-table
|
||||
[anchor2]: /nav/w/designpatterns/76.setup-table
|
||||
[anchor3]: Cue-Table-Figure-1.JPG
|
||||
[anchor4]: https://msdn.microsoft.com/en-us/library/dn789553(v=nav.90).aspx
|
||||
[anchor5]: https://msdn.microsoft.com/en-us/library/ff477101(v=nav.90).aspx
|
||||
|
||||
|
||||
[image0]: Cue-Table.png
|
||||
[image1]: Cue-Table-Figure-1.JPG
|
||||
BIN
content/NAVPatterns/currently-active-record/6545.Table.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
166
content/NAVPatterns/currently-active-record/index.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
+++
|
||||
title = "currently-active-record.md"
|
||||
weight = 450
|
||||
+++
|
||||
Authors: Henrik Langbak and Kim Ginnerup, Bording Data
|
||||
|
||||
## Abstract
|
||||
|
||||
Date controlled data is expensive to find in the database. This pattern describes how using a view with a sub-select and a linked table object will minimize the returned dataset.
|
||||
A side effect is reduced and simplified code, increased performance and a more scalable solution that is almost independent of the amount of records in the table.
|
||||
|
||||
## Description
|
||||
|
||||
There is no way in NAV to get a set of records from the database, which all have the newest starting date, that is less than or equal to today's date. Having an ending date on the record will help, but it introduces some other problems. In Dynamics NAV this is normally done by reading too many records, either at the SQL Server level or in the middle tier and throw away the ones you do not need. That is a waste of resources:
|
||||
|
||||
< --\[if supportLists\]--\>\* < --\[endif\]--\>The SQL Server is reading too many records
|
||||
|
||||
< --\[if supportLists\]--\>\* < --\[endif\]--\>There would be too much data sent over the network.
|
||||
(If the SQL Server and the NAV Service tier are on different machines.)
|
||||
|
||||
< --\[if supportLists\]--\>\* < --\[endif\]--\>The NAV Service Tier receives and throws away data.
|
||||
|
||||
### Ending Date Problem
|
||||
|
||||
Ending Date may introduce some problems of its own.
|
||||
|
||||
If your design requires to have one and only one active record per key in a dataset, then Ending Date introduces the possibility for overlapping or holes in the timeline.
|
||||
Ending Date creates a dependency between two records. Changing a Starting Date, requires you to update the previous record. Changing the Ending Date requires you to update the next record.
|
||||
If you add a record in between you will have to update both the before and the after record.
|
||||
|
||||
The pattern we describe here will work whether there is an Ending Date or Not.
|
||||
|
||||
The pattern is also relevant for other types than date. The pattern is usable whenever you have dependencies between rows in a table.
|
||||
|
||||
Use the pattern whenever you read a set of data containing a Starting Date and you need to implement a loop to throw away unwanted records. An example could be Codeunit 7000 "Sales Price Calc. Mgt.". In this codeunit there are many loop constructs to find prices and discounts.
|
||||
|
||||
## Usage
|
||||
|
||||
In the following example, we have a fictive table containing: Code, Starting Date and Price. The Primary Key consist of Code, Starting Date. The Database is the Demo Database, and the Company is Cronus.
|
||||
|
||||
< --\[if supportLineBreakNewLine\]--\>[![ ][image0]][anchor0]
|
||||
< --\[endif\]--\>
|
||||
|
||||
### 1\. Create the view
|
||||
|
||||
You will need to create the view before you define the Table Object.
|
||||
You will need to create a view for every company in the database.
|
||||
|
||||
CREATE VIEW \[dbo\].\[CRONUS$PriceView\]
|
||||
AS
|
||||
SELECT \[Code\], \[Starting Date\], \[Price\]
|
||||
FROM dbo.\[CRONUS$Price\] AS A
|
||||
WHERE \[Starting Date\] =
|
||||
(SELECT MAX(\[Starting Date\])
|
||||
FROM dbo.\[CRONUS$Price\] AS B
|
||||
WHERE B.\[Code\] = A.\[Code\] AND
|
||||
B.\[Starting Date\] <= GETDATE())
|
||||
|
||||
Test the view to ensure that you get the correct result. It is much easier to test now than later.
|
||||
|
||||
### 2\. Create the Table object
|
||||
|
||||
Remember to set the link table property before you save it.
|
||||
|
||||
### 3\. Implement the code
|
||||
|
||||
IF PriceView.FINDSET THEN // You have them
|
||||
|
||||
### 4\. Create a deployment codeunit
|
||||
|
||||
Create a SQL Deployment codeunit to manage your views.
|
||||
The codeunit needs to Create or Alter the views for all companies.
|
||||
To see an example of how to talk to SQL Server using .NET see Waldo's blog here:
|
||||
[http://dynamicsuser.net/blogs/waldo/archive/2011/07/19/net-interop-calling-stored-procedures-on-sql-server-example-1.aspx][anchor1]
|
||||
< --\[if supportLineBreakNewLine\]--\>
|
||||
< --\[endif\]--\>
|
||||
|
||||
### 5\. Deployment
|
||||
|
||||
You need to deploy in three steps:
|
||||
|
||||
< --\[if supportLists\]--\>1\. < --\[endif\]--\>Delete the table objects referencing the views
|
||||
|
||||
< --\[if supportLists\]--\>2\. < --\[endif\]--\>Deploy and run the deployment codeunit
|
||||
|
||||
< --\[if supportLists\]--\>3\. < --\[endif\]--\>Deploy the new table objects that reference the views
|
||||
|
||||
### General precaution
|
||||
|
||||
If you later want to change the view, you need to follow these rules:
|
||||
|
||||
< --\[if supportLists\]--\>\* < --\[endif\]--\>If you add columns, you need to add them to the view first and then add them to the Table Object.
|
||||
|
||||
< --\[if supportLists\]--\>\* < --\[endif\]--\>If you want to remove columns from the view, you need to delete the Table Object, then change the view and last recreate the Table Object without the new columns.
|
||||
|
||||
### Code example that accomplish the same but without using the pattern
|
||||
|
||||
This following example will give you the same result but the performance will deteriorate as time goes by and you get more and more old data.
|
||||
|
||||
Price.SETCURRENTKEY(Code,"Starting Date");
|
||||
|
||||
Price.SETFILTER("Starting Date",'..%1', TODAY
|
||||
|
||||
IF Price.FINDSET THEN BEGIN
|
||||
REPEAT
|
||||
|
||||
Price.SETRANGE(Code, Price.Code);
|
||||
|
||||
Price.FINDLAST;
|
||||
|
||||
Price.SETRANGE(Code);
|
||||
|
||||
PriceTemp := Price;
|
||||
|
||||
PriceTemp.INSERT;
|
||||
|
||||
UNTIL Price.NEXT = 0;
|
||||
|
||||
END;
|
||||
// PriceTemp will contain the Prices
|
||||
|
||||
### Comparison
|
||||
|
||||
The above NAV example is for a very simple date controlled solution and is provided to give an idea of what the pattern changes seen from a NAV development point of view. But consider the following:
|
||||
|
||||
< --\[if supportLists\]--\>\* < --\[endif\]--\>The table has a more complex key.
|
||||
This will require setting and clearing more filters
|
||||
|
||||
< --\[if supportLists\]--\>\* < --\[endif\]--\>You need to read from more than one table.
|
||||
Say you need to apply discount from a separate table.
|
||||
This may give several lines in PriceTemp.
|
||||
|
||||
< --\[if supportLists\]--\>\* < --\[endif\]--\>If the Code field is controlled by a Type field.
|
||||
The Code field reference keys in different tables
|
||||
|
||||
All three examples above can be implemented directly in the view. By using the pattern, it will still only require a single line of NAV code.
|
||||
|
||||
Using the pattern will only issue one SQL call and thereby one trip to the server.
|
||||
The NAV Example will require an unknown number of SQL calls and thereby an unknown number of trips to the server. The number of SQL calls is dependent on the number of distinct Code values.
|
||||
The NAV example will require SQL Server to read all data older than or equal to TODAY, but only return one row per Code. Over time, as old data piles up in the system, the NAV code will perform slower because the SQL statements will be slower.
|
||||
The Pattern makes a scalable solution with a predictable performance. The performance will not deteriorate at the same rate as the NAV code example.
|
||||
|
||||
## NAV Usages
|
||||
|
||||
The pattern does not exist in NAV (yet J). We have used it several times in our code for an Add-On.
|
||||
|
||||
## Ideas for improvement
|
||||
|
||||
|
||||
Query Object should be able to handle sub-selects and Unions. A simple solution could be to allow the NAV developer to specify the actual Select statement inside the query Object in clear text. Opening up for writing your own queries and map the projection to the Query-defined fields will make the query Object very versatile and remove the pressure from Microsoft trying to create all the different permutations that a select statement can have. Microsoft and others have all tried to create wizards that can create SQL select statement. They all end up having a clear text option.
|
||||
|
||||
An alternative would be better support for linked table objects, specifically views. The current implementation is very fragile.
|
||||
|
||||
The pattern only supports fetching data for a given date (normally today). This is because you cannot control the where-clause of the sub-select.
|
||||
|
||||
## Related Topics
|
||||
|
||||
The idea of having a linked table object pointing to a view could be a pattern of its own.
|
||||
|
||||
|
||||
|
||||
[anchor0]: 6545.Table.png
|
||||
[anchor1]: http://dynamicsuser.net/blogs/waldo/archive/2011/07/19/net-interop-calling-stored-procedures-on-sql-server-example-1.aspx
|
||||
|
||||
|
||||
[image0]: 6545.Table.png
|
||||
22
content/NAVPatterns/cyclomatic-complexity/index.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
+++
|
||||
title = "cyclomatic-complexity.md"
|
||||
weight = 460
|
||||
+++
|
||||
Do not write functions that have high cyclomatic complexity. This makes the code hard to maintain.
|
||||
|
||||
Bad code
|
||||
|
||||
Any procedure / trigger that has a cyclomatic complexity \> 25, using the CC3 version mentioned in [this article][anchor0].
|
||||
|
||||
|
||||
|
||||
Good code
|
||||
|
||||
Any procedure / trigger that has a cyclomatic complexity <= 25, using the CC3 version.
|
||||
The CC3 version is computed by summing the following in a code block:
|
||||
- each IF statement as 1\.
|
||||
- each entire CASE as 1\.
|
||||
|
||||
|
||||
|
||||
[anchor0]: http://www.aivosto.com/project/help/pm-complexity.html
|
||||
BIN
content/NAVPatterns/data-driven-blocked-entity/attention.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
107
content/NAVPatterns/data-driven-blocked-entity/index.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
+++
|
||||
title = "data-driven-blocked-entity.md"
|
||||
weight = 470
|
||||
+++
|
||||
_Written by Bogdan Andrei Sturzoiu, at Microsoft Development Center Copenhagen_
|
||||
|
||||
******Abstract**
|
||||
|
||||
This pattern implements a generic mechanism for dynamically restricting and allowing usage of a record by the business process administrator.
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
**Problem**
|
||||
|
||||
A NAV record can be used in a number of functionalities across the app. There are situations, however, when the administrator wants to restrict the consumption of such a record, as well as lift the restriction when it is no longer relevant.
|
||||
|
||||
For example, a new customer record should not be used for posting documents until it is approved by the relevant approver.
|
||||
|
||||
We could solve this by using the [Blocked Entity pattern][anchor1], but it requires database schema changes, which have an upgrade impact.
|
||||
|
||||
The blocked entity pattern involves:
|
||||
|
||||
1. Adding a "blocked" status field on the record (either a Boolean or in the more advanced cases, an option field refining the usage).
|
||||
2. Adding specific code for the record in every place where the restriction needs to be enforced.
|
||||
|
||||
In contrast, the Data-driven Blocked Entity pattern involves adding a new record (data change) to mark the restriction, instead of adding a new field (metadata change).
|
||||
|
||||
**Solution**
|
||||
|
||||
This pattern describes a generic mechanism of adding and lifting restrictions for any type of record.
|
||||
|
||||
The restriction mechanism has the following elements:
|
||||
|
||||
1. Adding a restriction record for a specific reason (e.g. the record requires approval), which will act as a surrogate key (unique identifier) for the restricted record. This can be implemented through a workflow response, or directly, by calling the Restriction Management codeunit function.
|
||||
2. Lifting the restriction when it is no longer necessary. Again, this can be done using a workflow response or directly by calling the dedicated function.
|
||||
3. Consuming the restriction in the places of interest for a specific purpose. This is an application feature that requires a call to the Restriction Management codeunit to check for restrictions.
|
||||
|
||||
Currently, the restrictions are record-based and type-less. They act as simple tokens, and they have:
|
||||
|
||||
* A reason (e.g. the record requires approval)
|
||||
* A purpose (e.g. the record cannot be posted).
|
||||
|
||||
You must make sure to differentiate between the reason and the purpose. That is because the restriction can only be added once per record, but consumed in multiple places.
|
||||
|
||||
******Example**
|
||||
|
||||
For example, we want to restrict posting Gen. Journal Lines if a customer has not been added in Account No. field.
|
||||
|
||||
For this, the following components are needed:
|
||||
|
||||
1. When a Gen. Journal Line is inserted, call RestrictRecordUsage in COD1550, either directly in the trigger or using an event subscriber.
|
||||
2. When you validate a Customer No. as Account no. and Customer as Account Type, lift the restrictions by calling AllowRecordUsage in COD1550\.
|
||||
3. The consumption of the restriction at posting is already implemented as an event in TAB81, OnCheckGenJournalLinePostRestrictions. No further action necessary.
|
||||
|
||||
**NAV Usage**
|
||||
|
||||
All the approval workflows include a response that restricts usage of a record, and then, at the end of an approval loop, a response that allows the usage again by lifting the restriction. See responses "Add record restriction" and "Remove record restriction" implemented in COD1521\.[
|
||||
][anchor2]
|
||||
|
||||
The code behind the "Add record restriction" workflow response:
|
||||
|
||||
RecRef.GETTABLE(Variant);
|
||||
Workflow.GET(WorkflowStepInstance."Workflow Code");
|
||||
RecordRestrictionMgt.RestrictRecordUsage(RecRef.RECORDID,STRSUBSTNO(RestrictUsageDetailsTxt,Workflow.Code,Workflow.Description));
|
||||
|
||||
|
||||
The code behind the "Remove record restriction" response:
|
||||
|
||||
RecRef.GETTABLE(Variant);
|
||||
CASE RecRef.NUMBER OF
|
||||
DATABASE::"Approval Entry":
|
||||
BEGIN
|
||||
RecordRestrictionMgt.AllowRecordUsage(RecRef.RECORDID);
|
||||
RecRef.SETTABLE(ApprovalEntry);
|
||||
RecRef.GET(ApprovalEntry."Record ID to Approve");
|
||||
AllowRecordUsage(RecRef);
|
||||
END;
|
||||
DATABASE::"Gen. Journal Batch":
|
||||
BEGIN
|
||||
RecRef.SETTABLE(GenJournalBatch);
|
||||
RecordRestrictionMgt.AllowGenJournalBatchUsage(GenJournalBatch);
|
||||
END
|
||||
ELSE
|
||||
RecordRestrictionMgt.AllowRecordUsage(RecRef.RECORDID);
|
||||
END;
|
||||
|
||||
|
||||
Notice****how lifting a restriction for a Gen. Journal Batch involves lifting all the restrictions for the individual journal lines in the batch (hence the special branching of the code).
|
||||
|
||||
******Consequences**
|
||||
|
||||
Currently, there can only be one restriction per record. There are no restriction types.
|
||||
|
||||
In the future, a type field should be added to the restriction table, to allow adding restrictions for different purposes, and to refine their consumption. For example, a posting restriction might only be enforced for restrictions originating from approvals.
|
||||
|
||||
**NAV** **Versions**
|
||||
|
||||
This pattern has been introduced in Dynamics NAV 2016\.
|
||||
|
||||
|
||||
|
||||
[anchor0]: attention.jpg
|
||||
[anchor1]: /nav/w/designpatterns/79.blocked-entity
|
||||
[anchor2]: https://microsoft.sharepoint.com/teams/DynamicsNAV/Wiki/Nav%20Wiki%20Documents/NAV%20App%20Patterns/NAV%20App%20Patterns%20for%20Review/Data-Driven%20Blocked%20Entity.docx#_msocom_2
|
||||
|
||||
|
||||
[image0]: attention.jpg
|
||||
BIN
content/NAVPatterns/data-migration-facade/8308.logo.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
content/NAVPatterns/data-migration-facade/NoStagingTableNew2.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
content/NAVPatterns/data-migration-facade/StagingTableNew2.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
content/NAVPatterns/data-migration-facade/errorhandling1.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
content/NAVPatterns/data-migration-facade/errorhandling2.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
284
content/NAVPatterns/data-migration-facade/index.md
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
+++
|
||||
title = "data-migration-facade.md"
|
||||
weight = 480
|
||||
+++
|
||||
# **Data Migration Façade**
|
||||
|
||||
_By David Bastide and Soumya Dutta at Microsoft Development Center Copenhagen_
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
## **Context:**
|
||||
|
||||
This pattern is describing how you can migrate data using the Data Migration Façade.
|
||||
|
||||
## **Problem:**
|
||||
|
||||
Writing migration code from an external source, such as a product from a competitor, can be time consuming, as you need to tackle the problems of what to migrate, in which order, exception handling... and can result in code that is fragile due to deep dependencies on the NAV data model (high coupling). Any change to the NAV data model can easily break this code. ****
|
||||
|
||||
## **Solution:**
|
||||
|
||||
The idea of the Data Migration Façade is to provide an API that creates and updates master data and related entities (including transactions) without referencing NAV tables.
|
||||
|
||||
Additionally, the framework around the Data Migration Façade provides tools for error handling, and a way to integrate the migration in the Data Migration Overview page (page 1799).
|
||||
|
||||
The façade framework has the following components:
|
||||
|
||||
* Two management codeunits:
|
||||
* **Data Migration Façade** (codeunit 6100): Integrates the extension to the Data Migration Wizard. Starts a migration, or restarts a migration that failed for some records.
|
||||
* **Data Migration Status Facade** (codeunit 6101): Initializes and updates the status of the migration. The status displays in the **Data Migration Overview** page (page 1799).
|
||||
|
||||
* Several master data migration façade codeunits that create and update entities. Each codeunit also contains events that help ensure that data is created in the correct order:
|
||||
* **GL Acc. Data Migration Façade** (codeunit 6110)
|
||||
* **Vendor Data Migration Façade** (codeunit 6111)
|
||||
* **Customer Data Migration Façade** (codeunit 6112)
|
||||
* **Item Data Migration Façade** (codeunit 6113)
|
||||
* If you want to migrate other entities, it is possible to define your own codeunit that will contain your custom code (see 'Usage' below).
|
||||
|
||||
* A page where you can view the status and progress of the migration. It shows one line for each master data entity (item, customer, vendor, general ledger account) that was chosen for migration. You can also stop a migration by choosing the **Stop Migration** action.
|
||||
|
||||
* * **Data Migration Overview** (page 1799)
|
||||
|
||||
## **Usage:**
|
||||
|
||||
There are the following use cases:
|
||||
|
||||
* Migration with staging tables, where data from another product is exported to a file or set of files, and the exported data is then imported to buffer tables, before running the migration logic. In this case, the migration is implemented in your extension codeunits, and it is called through events, one record at a time.
|
||||
|
||||
* Migration without staging tables, for example, when you migrate data by importing it from an external tool such as external APIs and webservices. In this case, the migration is also implemented in your extension codeunits but it will be called through the OnRun procedure. You will be responsible for looping on the records to migrate, and you must migrate all records in this unique OnRun call for a given entity.
|
||||
|
||||
To initialize and start the data migration, you must call the following procedures:
|
||||
|
||||
* **"Data Migration Status Façade".InitStatusLine('My Migration Type',Database::Item,42000,0,Codeunit::"My extension Item migration codeunit") **
|
||||
This deletes existing status lines for migrating Items for 'My Migration Type', and initializes a new status line with 0 records migrated out of 42000\.
|
||||
|
||||
**"Data Migration Façade".StartMigration('My Migration Type',false) **
|
||||
This starts the migration. False means this is not a retry. A re-try is when you migrate one or more records from the **Show Errors** page, which is described later in this document. Retry = true is only used by the **Show Errors** page and should not be used from extensions.
|
||||
|
||||
### **Usage without staging tables:**
|
||||
|
||||
The overall workflow is:
|
||||
|
||||
1. Integrate your extension in the **Data Migration Wizard** by subscribing to the events exposed by the **Data Migration Façade**.
|
||||
2. From there, initialize the status of the migration so it can be displayed in the Data Migration Overview. When initializing the status lines, you provide the codeunit ID that will be called for each entity: **"Data Migration Status Façade".InitStatusLine('My Migration Type',Database::Item,42000,0,Codeunit::"My extension Item migration codeunit")**.
|
||||
3. Launch the migration: **"Data Migration Façade".StartMigration('My Migration Type',false).**
|
||||
4. Your migration codeunits are called one at a time (**OnRun**) in the following order:
|
||||
1. G/L accounts (first, because customer/vendor posting groups refer to G/L accounts)
|
||||
2. Customers
|
||||
3. Vendors
|
||||
4. Items (because discounts may refer to customer groups)
|
||||
5. Others
|
||||
5. Loop on all of the records to migrate. You can update the increment of the amount of records migrated: **"Data Migration Status Facade".IncrementStatusLine('My Migration Type',Database::Item,42).**
|
||||
|
||||
[![ ][image1]][anchor1]
|
||||
|
||||
_Figure 1: sequence diagram of the data migration without staging tables _
|
||||
|
||||
The following example shows how to migrate items without staging tables:
|
||||
|
||||
trigger OnRun();
|
||||
var
|
||||
ItemDataMigrationFacade: Codeunit "Item Data Migration Facade";
|
||||
ItemNumber: Integer;
|
||||
ItemJson: Text;
|
||||
begin
|
||||
// loop on items retrieved through a web service for example
|
||||
for ItemNumber := 1 to ExternalWebService.GetItemCount do begin
|
||||
ExternalWebService.GetItem(ItemNumber,ItemJson);
|
||||
// create item using the facade
|
||||
if not ItemDataMigrationFacade.CreateItemIfNeeded(ItemJson.ItemNumber,ItemJson.ItemName1,
|
||||
ItemJson.ItemName2,ConvertItemType(ItemJson.ItemType)) then
|
||||
exit; // item already exists
|
||||
// set some fields using the facade
|
||||
ItemDataMigrationFacade.SetVendorItemNo(ItemJson.VendItemNumber);
|
||||
ItemDataMigrationFacade.SetUnitVolume(ItemJson.Volume);
|
||||
ItemDataMigrationFacade.SetAlternativeItemNo(ItemJson.AltItemNumber);
|
||||
if ItemJson.PrimaryVendor <\> '' then
|
||||
ItemDataMigrationFacade.SetVendorNo(ItemJson.PrimaryVendor);
|
||||
// migrate dependencies
|
||||
MigrateItemUnitOfMeasure(ItemDataMigrationFacade,ItemJson);
|
||||
// modify the item (+run trigger) to save the changes made by setters
|
||||
ItemDataMigrationFacade.ModifyItem(true);
|
||||
// update the status in the migration dashboard
|
||||
DataMigrationStatusFacade.IncrementMigratedRecordCount('My Migration Type',
|
||||
Database::Item,1);
|
||||
end;
|
||||
end;
|
||||
procedure MigrateItemUnitOfMeasure(ItemDataMigrationFacade : Codeunit "Item Data Migration Facade";
|
||||
ItemJson : Text);
|
||||
var
|
||||
MyUnitCodeStagingTable: Record "My Unit Code Staging Table";
|
||||
DataMigrationStatusFacade: Codeunit "Data Migration Status Facade";
|
||||
DescriptionToSet: Text\[10\];
|
||||
UnitCodeJson: Text;
|
||||
begin
|
||||
if ItemJson.UnitCode = '' then
|
||||
// log an error using the Data migration façade
|
||||
DataMigrationStatusFacade.RegisterErrorNoStagingTablesCase(
|
||||
'My Migration Type',Database::Item,'Unit of measure is empty.');
|
||||
if ExternalWebService.GetUnitCode(ItemJson.UnitCode,UnitCodeJson) then
|
||||
DescriptionToSet := UnitCodeJson.Description;
|
||||
ItemDataMigrationFacade.CreateUnitOfMeasureIfNeeded(ItemJson.UnitCode, DescriptionToSet);
|
||||
// set the unit of measure on the item
|
||||
ItemDataMigrationFacade.SetBaseUnitOfMeasure(ItemJson.UnitCode);
|
||||
end;
|
||||
|
||||
|
||||
_Figure 2: Example of Item and Item Unit of Measure migration without staging tables _
|
||||
|
||||
### **Usage with staging tables:**
|
||||
|
||||
The overall workflow is:
|
||||
|
||||
* Integrate your extension in the Data Migration Wizard by subscribing to the events exposed by the **Data Migration Façade**.
|
||||
|
||||
* From there, initialize the status of the migration so it can be displayed in the **Data Migration Overview: "Data Migration Status Façade".InitStatusLine('My Migration Type',Database::Item,42000,Database::"My extension Staging table for items",0)**.
|
||||
* You can either fill the staging tables from the wizard events, or in a subscriber to the event dedicated to filling staging tables (**'OnFillStagingTables'** in codeunit 6100): in this case, the import of data from the files to the staging tables will be done in the background.
|
||||
* Launch the migration: **"Data Migration Façade".StartMigration('My Migration Type',false)**.
|
||||
* Subscribe to the events to migrate entities and their dependencies.
|
||||
* From the event subscribers, call the façade procedures to create entities and set their field values:
|
||||
|
||||
* * **"Item Data Migration Façade".CreateItemIfNeeded('ITEM042','My Item Description';'My Item Description 2';ItemTypeToSet::Inventory)**
|
||||
*
|
||||
**"Item Data Migration Façade".SetBaseUnitOfMeasure('BOX')**
|
||||
*
|
||||
**"Item Data Migration Façade".ModifyItem(true)**
|
||||
|
||||
[![ ][image2]][anchor2]
|
||||
|
||||
_Figure 3: Simplified sequence diagram of the data migration with staging tables _
|
||||
|
||||
Below is a simplified example showing how to create an item:
|
||||
|
||||
\[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Data Migration Facade", 'OnMigrateItem', '', true, true)\]
|
||||
procedure OnMigrateItem(VAR Sender : Codeunit "Item Data Migration Facade";RecordIdToMigrate : RecordId);
|
||||
var
|
||||
MyItemStagingTable : Record "My Item Staging Table";
|
||||
begin
|
||||
// handle the event if it targets this extension's staging table
|
||||
if RecordIdToMigrate.TableNo <\> Database::"My Item Staging Table" then
|
||||
exit;
|
||||
MyItemStagingTable.Get(RecordIdToMigrate);
|
||||
// create item using the facade
|
||||
if not Sender.CreateItemIfNeeded(MyItemStagingTable.ItemNumber,MyItemStagingTable.ItemName1,
|
||||
MyItemStagingTable.ItemName2,ConvertItemType(MyItemStagingTable.ItemType)) then
|
||||
exit; // item already exists
|
||||
// set some fields using the facade
|
||||
Sender.SetVendorItemNo(MyItemStagingTable.VendItemNumber);
|
||||
Sender.SetUnitVolume(MyItemStagingTable.Volume);
|
||||
Sender.SetAlternativeItemNo(MyItemStagingTable.AltItemNumber);
|
||||
if MyItemStagingTable.PrimaryVendor <\> '' then
|
||||
Sender.SetVendorNo(MyItemStagingTable.PrimaryVendor);
|
||||
// modify the item (+run trigger) to save the changes made by setters
|
||||
Sender.ModifyItem(true);
|
||||
end;
|
||||
|
||||
|
||||
_Figure 4: Example of event subscriber for Item migration _
|
||||
|
||||
__
|
||||
|
||||
Below is another example showing how to use additional events to set fields that reference other tables, here the unit of measure:
|
||||
|
||||
\[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Data Migration Facade", 'OnMigrateItemUnitOfMeasure', '', true, true)\]
|
||||
procedure OnMigrateItemUnitOfMeasure(VAR Sender : Codeunit "Item Data Migration Facade";RecordIdToMigrate : RecordId);
|
||||
var
|
||||
MyItemStagingTable : Record "My Item Staging Table";
|
||||
MyUnitCodeStagingTable : Record "My Unit Code Staging Table";
|
||||
DescriptionToSet: Text\[10\];
|
||||
begin
|
||||
// handle the event if it targets this extension's staging table
|
||||
if RecordIdToMigrate.TableNo <\> Database::"My Item Staging Table" then
|
||||
exit;
|
||||
MyItemStagingTable.Get(RecordIdToMigrate);
|
||||
if MyItemStagingTable.UnitCode = '' then
|
||||
error('Unit of measure is empty.');
|
||||
MyUnitCodeStagingTable.SetRange(UnitCode,MyItemStagingTable.UnitCode);
|
||||
if MyUnitCodeStagingTable.FindFirst then
|
||||
DescriptionToSet := MyUnitCodeStagingTable.Description;
|
||||
// create the unit of measure through the facade
|
||||
Sender.CreateUnitOfMeasureIfNeeded(MyItemStagingTable.UnitCode, DescriptionToSet);
|
||||
// set the unit of measure on the item
|
||||
Sender.SetBaseUnitOfMeasure(MyItemStagingTable.UnitCode);
|
||||
// modify the item to save the changes made by setter
|
||||
Sender.ModifyItem(false);
|
||||
end;
|
||||
|
||||
_Figure 5: Example of event subscriber for Item Unit of Measure migration _
|
||||
|
||||
### **Combining both approaches:**
|
||||
|
||||
If you want to migrate additional entities, the **Data Migration **framework lets you initialize the migration with entities other than master data. In this case, the **Data Migration Overview** page will show additional lines. Item, vendor, customer, an general ledger accounts are migrated with an event driven approach, and the additional entities are migrated by calling an extension codeunit **OnRun** method.
|
||||
|
||||
## **Error handling with staging tables:**
|
||||
|
||||
The migration starts by calling **RUN** on the façade codeunit. Errors thrown during the call are captured by **GETLASTERRORTEXT** and displayed when you choose the **Show Errors** action on the **Data Migration Overview** page.
|
||||
|
||||
[![ ][image3]][anchor3]
|
||||
|
||||
_Figure 6: List of errors shown when clicking **Show Errors** on the **Data Migration Overview** page _
|
||||
|
||||
__
|
||||
|
||||
The **Edit Record** action opens a view of the staging table, where you can edit fields to fix errors. Figure 4 shows an example of a page for the vendor staging table in a migration from C5\.
|
||||
|
||||
[![ ][image4]][anchor4]
|
||||
|
||||
_Figure 7: Edit a staging table record _
|
||||
|
||||
__
|
||||
|
||||
The **Staging Table ID** determines the page to open, so it is important that the page ID is equal to the **Staging Table ID**, at least for the master data staging tables, for example, for**G/L Accounts, Items, Customers** and **Vendor**. You should ensure that pages to edit related entities are linked on this page by means of new actions. For example, Figure 4 uses the **C5 Purchaser** action.
|
||||
|
||||
After you fix the staging table record, you can choose the **Migrate** action to mark the selected records as records to retry, and then run **StartMigration** with the **Retry** flag set to true. This is the only place where the retry flag should be set to true in the **StartMigration** procedure.
|
||||
|
||||
Error handling without staging tables
|
||||
|
||||
When migrating data without staging tables, errors can be registered manually by the extension using **DataMigrationStatusFacade.RegisterErrorNoStagingTablesCase**. Otherwise they can be registered automatically if the codeunits fail when called on their **OnRun** procedure.
|
||||
|
||||
Errors will be displayed in the error list, but you cannot open and edit records because there is no staging table. The Edit action will not be available.
|
||||
|
||||
## **Limitations:**
|
||||
|
||||
* Data migration will fail if there are customers, vendors, items in the database and if these entities are selected for migration. For example, if you choose to migrate items and your company already contains items, you will get an error. This should not be an issue if you migrate your data from another tool to NAV, in which case you will most likely start on a fresh empty company. However, if you just want to import additional items to a company with existing items, then it is not supported by the framework. however, you can still use the different functions provided by the different codeunits (such as **Item Data Migration Facade**) to create the entities without strong coupling on the NAV data model.
|
||||
* **G/L entries** are deleted automatically.
|
||||
|
||||
* There is no automated rollback in case of failure: data that is successfully migrated will be commited, and data that is not successfully migrated with be shown in the errors list. The retry feature (in case of staging tables) then makes it possible for you to retry individual entities or ignore them.
|
||||
|
||||
## **Usages in NAV:**
|
||||
|
||||
The Data Migration Façade is available starting from version 2018\.
|
||||
|
||||
The following Façade codeunits for data migration management are available:
|
||||
|
||||
* COD6100 (**Data Migration Facade**)
|
||||
|
||||
* COD6101 (**Data Migration Status Facade**)
|
||||
|
||||
The following Entity data migration façade codeunits are available:
|
||||
|
||||
* COD6110 (**GL Acc. Data Migration Facade**)
|
||||
* COD6111 (**Vendor Data Migration Facade**)
|
||||
* COD6112 (**Customer Data Migration Facade**)
|
||||
|
||||
* COD6113 (**Item Data Migration Facade**)
|
||||
* COD6114 (**Ex. Rate Data Migration Facade**)
|
||||
|
||||
## **References:**
|
||||
|
||||
Façade pattern on Wikipedia: https://en.wikipedia.org/wiki/Facade\_pattern
|
||||
|
||||
|
||||
|
||||
[anchor0]: 8308.logo.png
|
||||
[anchor1]: NoStagingTableNew2.png
|
||||
[anchor2]: StagingTableNew2.png
|
||||
[anchor3]: errorhandling1.png
|
||||
[anchor4]: errorhandling2.png
|
||||
|
||||
|
||||
[image0]: 8308.logo.png
|
||||
[image1]: NoStagingTableNew2.png
|
||||
[image2]: StagingTableNew2.png
|
||||
[image3]: errorhandling1.png
|
||||
[image4]: errorhandling2.png
|
||||
11
content/NAVPatterns/design/index.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
+++
|
||||
title = "design.md"
|
||||
weight = 490
|
||||
+++
|
||||
## C/AL Coding Guidelines
|
||||
|
||||
## **Design**
|
||||
|
||||
###
|
||||
|
||||
Find the C/AL guidelines by expanding the menu in the left.
|
||||
BIN
content/NAVPatterns/discovery-event/Pic2.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
content/NAVPatterns/discovery-event/Pic2b.jpg
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
content/NAVPatterns/discovery-event/Pic3.jpg
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
content/NAVPatterns/discovery-event/Pic4.jpg
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
content/NAVPatterns/discovery-event/ServiceConnections.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
85
content/NAVPatterns/discovery-event/index.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
+++
|
||||
title = "discovery-event.md"
|
||||
weight = 500
|
||||
+++
|
||||
by waldo
|
||||
|
||||
# Abstract
|
||||
|
||||
The "Discovery Event" pattern is a way for a generic functionality, to call out to other functionalities that want to make use of it, by raising an event, so that they have an event to subscribe to. This is usually done to set itself up within the generic app.
|
||||
|
||||
# The problem
|
||||
|
||||
Let's suppose you have a generic piece of functionality, that hooks into lots of places (modules) in your application. To set this up, you might have to hook into all these parts of the application. Well, this pattern turns this setup around: let all the different modules set itself up in the generic app by raising a "discovery event".
|
||||
|
||||
# Usage
|
||||
|
||||
The pattern is most easily described when you look at an example. This example is an actual usage of the pattern within the application, in page **Service Connections.**
|
||||
|
||||
[![ ][image0]][anchor0]
|
||||
|
||||
The goal of this functionality is to:
|
||||
• List all the different connections to external services,
|
||||
• Have a central place to navigate to the corresponding setup of the service.
|
||||
|
||||
The functionality (**Service Connections**) itself, is not aware of the state nor setup nor any context of all the different services in the list. All it does is:
|
||||
• It raises an event as an opportunity for all services within the NAV application to subscribe to,
|
||||
• It has a public function **InsertServiceConnection** that the subscribers can use to register itself at the Service Connection.
|
||||
|
||||
The event **OnRegisterServiceConnection** is raised when the page (1279 - **Service Connections**) is opened.
|
||||
|
||||
One example of a subscription is the SMTP setup. In Codeunit 400 you'll find the subscriber function **HandleSMTPRegisterServiceConnection** which subscribes to this discovery event, and calls the **InsertServiceConnection** to register itself.
|
||||
|
||||
# Description
|
||||
|
||||
The main idea of this pattern is: "Discover the settings, the context, the records, ... which I need for my functionality" or "Discover the configuration for my functionality". In any case, "discover" is the main idea. It's a pattern where using both publishers and subscribers in one application makes a lot of sense.
|
||||
Let's break down to the steps that are needed to implement the pattern.
|
||||
|
||||
## Step 1: Publish the event
|
||||
|
||||
In the below example, I create a table **Module Status** with a published event **OnDiscoverModuleStatuses**.
|
||||
|
||||
[![ ][image1]][anchor1]
|
||||
|
||||
You see that I also include the sender. This way, I will be able to access the methods on my table (which I use as a class). Obviously, other patterns can be applied here as well, like the Argument Table pattern.
|
||||
|
||||
## Step 2: Raise the event on the right place
|
||||
|
||||
When you publish an event, it should obviously be raised somewhere in the code as well. In the below example, I want to raise the event simply by a method which I want to call from a page. So I create a global function where I raise the event:
|
||||
|
||||
[![ ][image2]][anchor2]
|
||||
|
||||
## Step 3: Create one or more global functions, so that your subscriber can call into your functionality to configure, set up, or do whatever it needs to do to make itself discoverable
|
||||
|
||||
The generic functionality that I want to call, should be part of the main class - in this case the**Module Discovery** class, or better, the table (**Module Status**). In this table, I create this global function, because I want to make it available for the subscribers:
|
||||
|
||||
[![ ][image3]][anchor3]
|
||||
The business logic doesn't really matter for this pattern. This is obviously dependent on the functionality where you would like to implement the pattern.
|
||||
|
||||
## Step 4: subscribe from the places in the app to this event, use the global function(s)
|
||||
|
||||
This could be anywhere. Any module within your vertical, of within the main application, can subscribe to the event. In the example below, I create the subscriber in Codeunit80, as I was interested in the status of the Sales-module in default NAV.
|
||||
The exact place of the subscriber is up to you. The main message is that it's part of the module that wants to subscribe, and not part of the **Module Status** module in the application.
|
||||
Here is the subscriber (and one small helper function):
|
||||
|
||||
[![ ][image4]][anchor4]
|
||||
You see I can use the "sender" as a normal Record-variable. I access the previously created global function to "register" this sales-module.
|
||||
|
||||
# Microsoft Dynamics NAV Versions
|
||||
|
||||
This pattern only works with Microsoft Dynamics **NAV 2016 and up.**
|
||||
|
||||
|
||||
|
||||
[anchor0]: ServiceConnections.jpg
|
||||
[anchor1]: Pic2.jpg
|
||||
[anchor2]: Pic2b.jpg
|
||||
[anchor3]: Pic3.jpg
|
||||
[anchor4]: Pic4.jpg
|
||||
|
||||
|
||||
[image0]: ServiceConnections.jpg
|
||||
[image1]: Pic2.jpg
|
||||
[image2]: Pic2b.jpg
|
||||
[image3]: Pic3.jpg
|
||||
[image4]: Pic4.jpg
|
||||
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 42 KiB |