From e4f08bf9a7295dd00cd848cd6ea989884d93831e Mon Sep 17 00:00:00 2001 From: TheDoubleH Date: Fri, 11 Feb 2022 17:30:04 +0000 Subject: [PATCH] deploy: 9e06f72056776e1281b2f2e184cfe7518fa1d21f --- 404.html | 16 +- bcbestpractices/index.html | 40 +- .../begin-as-an-afterword/index.html | 40 +- .../readability/begin-end/index.html | 40 +- .../binary-operator-line-start/index.html | 40 +- .../readability/case-actions/index.html | 40 +- .../readability/comments-spacing/index.html | 40 +- .../readability/end-else-pair/index.html | 40 +- bcbestpractices/readability/index.html | 40 +- .../keyword-pairs-indentation/index.html | 40 +- .../line-start-keywords/index.html | 40 +- .../readability/lonely-repeat/index.html | 40 +- .../readability/named-invocations/index.html | 40 +- .../one-statement-per-line/index.html | 40 +- .../separate-if-and-else/index.html | 40 +- .../spacing-binary-operators/index.html | 40 +- .../suggested-abbreviations/index.html | 40 +- .../readability/unnecessary-else/index.html | 40 +- .../unnecessary-truefalse/index.html | 40 +- .../readability/variable-naming/index.html | 40 +- .../variables-declarations-order/index.html | 40 +- bcpatterns/facade-pattern/index.html | 40 +- bcpatterns/generic-method-pattern/index.html | 40 +- bcpatterns/index.html | 40 +- categories/index.html | 40 +- contributing/forkandpr/index.html | 40 +- contributing/formattingtips/index.html | 40 +- contributing/index.html | 40 +- contributing/templates/guidelines/index.html | 40 +- contributing/templates/index.html | 40 +- contributing/templates/patterns/index.html | 40 +- contributing/theapprovalprocess/index.html | 40 +- discussions/index.html | 40 +- index.html | 40 +- index.json | 2 +- .../1-patterns/activity-log/index.html | 40 +- .../1-patterns/argument-table/index.html | 44 +- .../data-driven-blocked-entity/index.html | 51 +- .../1-patterns/blocked-entity/index.html | 44 +- .../cached-web-service-calls/index.html | 76 +-- .../conditional-cascading-update/index.html | 40 +- .../1-patterns/copy-document/index.html | 60 ++- .../create-data-from-templates/index.html | 92 ++-- .../create-urls-to-nav-clients/index.html | 481 +++++++++--------- .../creating-custom-charts/index.html | 67 +-- .../cross-session-events/index.html | 273 ++++++---- .../currently-active-record/index.html | 67 ++- .../data-migration-facade/index.html | 260 +++++----- .../1-patterns/discovery-event/index.html | 56 +- navpatterns/1-patterns/document/index.html | 42 +- .../index.html | 89 ++-- .../error-message-processing/index.html | 65 +-- .../index.html | 186 ++++--- .../index.html | 42 +- navpatterns/1-patterns/hooks/index.html | 55 +- .../index.html | 87 ++-- navpatterns/1-patterns/index.html | 40 +- navpatterns/1-patterns/index.xml | 5 +- .../instructions-in-the-ui/index.html | 58 +-- .../integration-of-addresses/index.html | 40 +- .../journal-error-processing/index.html | 127 ++--- .../journal-template-batch-line/index.html | 114 +++-- navpatterns/1-patterns/master-data/index.html | 189 ++++--- .../1-patterns/multi-file-download/index.html | 102 ++-- .../1-patterns/multi-page-list/index.html | 146 +++--- .../multilanguage-application-data/index.html | 111 ++-- .../net-exception-handling-in-cal/index.html | 262 +++++----- .../index.html | 300 ++++++----- navpatterns/1-patterns/no-series/index.html | 305 ++++++----- .../in-context-notifications/index.html | 48 +- .../1-patterns/notifications/index.html | 40 +- .../index.html | 118 +++-- navpatterns/1-patterns/observer/index.html | 135 +++-- .../index.html | 52 +- .../1-patterns/product-name/index.html | 40 +- navpatterns/1-patterns/queries/index.html | 40 +- navpatterns/1-patterns/queries/index.xml | 3 +- .../select-distinct-with-queries/index.html | 55 +- .../index.html | 110 ++-- .../index.html | 303 ++++++----- .../index.html | 71 ++- .../1-patterns/released-entity/index.html | 79 +-- .../1-patterns/report-selection/index.html | 123 +++-- .../1-sensitive-data-encapsulation/index.html | 94 ++-- .../security/2-data-encryption/index.html | 187 +++---- .../3-single-point-of-access/index.html | 85 ++-- .../security/4-masked-text/index.html | 89 ++-- .../security/5-ssl-in-nav/index.html | 82 ++- navpatterns/1-patterns/security/index.html | 50 +- navpatterns/1-patterns/security/index.xml | 20 - .../setup-specificity-fallback/index.html | 107 ++-- .../index.html | 76 +-- navpatterns/1-patterns/singleton/index.html | 42 +- navpatterns/1-patterns/singleton/index.xml | 2 - .../singleton/singleton-codeunit/index.html | 84 ++- .../singleton-table/cue-table/index.html | 178 ++++--- .../singleton/singleton-table/index.html | 101 ++-- .../singleton-table/setup-table/index.html | 55 +- .../1-patterns/standard-journal/index.html | 40 +- .../0250.Temporary-Dataset-Report-1.png | Bin 0 -> 46629 bytes .../2376.Temporary-Dataset-Report-2.png | Bin 0 -> 85238 bytes .../7607.Temporary-Dataset-Report-3.png | Bin 0 -> 5708 bytes .../temporary-dataset-report/index.html | 113 ++-- .../Fig-11.jpg | Bin 0 -> 54058 bytes .../Fig-12.jpg | Bin 0 -> 4847 bytes .../index.html | 45 +- .../transfer-custom-fields/index.html | 40 +- .../1-patterns/variant-facade/index.html | 212 ++++---- navpatterns/2-anti-patterns/index.html | 40 +- .../2-anti-patterns/nav-upgrade/index.html | 41 +- .../2-anti-patterns/reusable-bugs/index.html | 40 +- .../design/by-reference-parameters/index.html | 56 +- .../design/class-coupling/index.html | 42 +- .../design/cyclomatic-complexity/index.html | 42 +- .../index.html | 40 +- .../findset-findfirst-findlast/index.html | 56 +- .../3-cal-coding-guidelines/design/index.html | 40 +- .../3-cal-coding-guidelines/design/index.xml | 30 +- .../design/initialized-variables/index.html | 74 +-- .../design/maintainability-index/index.html | 50 +- .../design/parameter-placeholders/index.html | 45 +- .../static-object-invocation/index.html | 46 +- .../design/unreachable-code/index.html | 56 +- .../unused-initialized-variables/index.html | 82 +-- .../design/unused-variables/index.html | 66 +-- .../variable-capacity-mismatch/index.html | 76 +-- .../with-scope-name-collision/index.html | 59 +-- .../3-cal-coding-guidelines/index.html | 40 +- .../internally-used-dot-net-types/index.html | 40 +- .../internationalization/index.html | 40 +- .../internationalization/index.xml | 7 +- .../using-calcdate/index.html | 52 +- .../captionml-for-system-tables/index.html | 40 +- .../fieldcaption-and-tablecaption/index.html | 49 +- .../global-text-constants/index.html | 63 ++- .../localizability/index.html | 40 +- .../localizability/index.xml | 15 +- .../use-text-constants/index.html | 91 ++-- .../using-optioncaptionml/index.html | 40 +- .../begin-as-an-afterword/index.html | 58 +-- .../readability/begin-end/index.html | 120 +++-- .../binary-operator-line-start/index.html | 60 +-- .../readability/blank-lines/index.html | 87 ++-- .../readability/case-actions/index.html | 64 +-- .../colon-usage-in-case/index.html | 60 +-- .../comments-curly-brackets/index.html | 92 ++-- .../readability/comments-spacing/index.html | 53 +- .../readability/end-else-pair/index.html | 71 ++- .../readability/indentation/index.html | 157 +++--- .../readability/index.html | 40 +- .../readability/index.xml | 166 +++--- .../keyword-pairs-indentation/index.html | 58 +-- .../line-start-keywords/index.html | 70 +-- .../readability/lonely-repeat/index.html | 56 +- .../readability/named-invocations/index.html | 53 +- .../readability/nested-withs/index.html | 71 ++- .../one-statement-per-line/index.html | 68 +-- .../separate-if-and-else/index.html | 64 +-- .../spacing-binary-operators/index.html | 71 ++- .../spacing-brackets-and/index.html | 71 ++- .../spacing-unary-operators/index.html | 61 ++- .../suggested-abbreviations/index.html | 40 +- .../temporary-variable-naming/index.html | 60 ++- .../readability/textconst-suffixes/index.html | 173 +++---- .../unary-operator-line-end/index.html | 57 +-- .../index.html | 72 ++- .../readability/unnecessary-else/index.html | 62 +-- .../index.html | 61 ++- .../unnecessary-separators/index.html | 52 +- .../unnecessary-truefalse/index.html | 61 ++- .../variable-already-scoped/index.html | 77 ++- .../readability/variable-naming/index.html | 115 ++--- .../variables-declarations-order/index.html | 56 +- .../ux/actions-images/index.html | 63 ++- .../ux/confirm/index.html | 61 ++- .../ux/fielderror/index.html | 61 ++- .../3-cal-coding-guidelines/ux/index.html | 40 +- .../3-cal-coding-guidelines/ux/index.xml | 20 +- .../ux/message-and-error/index.html | 61 ++- .../4-get-involved/code-of-conduct/index.html | 40 +- navpatterns/4-get-involved/index.html | 46 +- .../patterns-authors/index.html | 42 +- .../index.html | 40 +- navpatterns/index.html | 40 +- navpatterns/related-links/index.html | 40 +- tags/index.html | 40 +- welcome/index.html | 40 +- 187 files changed, 6620 insertions(+), 6342 deletions(-) create mode 100644 navpatterns/1-patterns/temporary-dataset-report/0250.Temporary-Dataset-Report-1.png create mode 100644 navpatterns/1-patterns/temporary-dataset-report/2376.Temporary-Dataset-Report-2.png create mode 100644 navpatterns/1-patterns/temporary-dataset-report/7607.Temporary-Dataset-Report-3.png create mode 100644 navpatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/Fig-11.jpg create mode 100644 navpatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/Fig-12.jpg diff --git a/404.html b/404.html index 9c8c216f..93c12e22 100644 --- a/404.html +++ b/404.html @@ -5,14 +5,14 @@ 404 Page not found :: AL Guidelines - - - - - - - - + + + + + + + + diff --git a/bcbestpractices/index.html b/bcbestpractices/index.html index d77c8d58..f1dd1b3a 100644 --- a/bcbestpractices/index.html +++ b/bcbestpractices/index.html @@ -6,16 +6,16 @@ BC Dev. Best Practices :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev - - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
    @@ -271,7 +271,7 @@ ALGuidelines.Dev

    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, but it requires database schema changes, which have an upgrade impact.

    +

    We could solve this by using the Blocked Entity pattern, 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. @@ -304,11 +304,10 @@ ALGuidelines.Dev

      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.

      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);
      +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":
      @@ -344,12 +343,12 @@ RecordRestrictionMgt.RestrictRecordUsage(RecRef.RECORDID,STRSUBSTNO(RestrictUsag
       
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/blocked-entity/index.html b/navpatterns/1-patterns/blocked-entity/index.html index c07c6d46..33641a41 100644 --- a/navpatterns/1-patterns/blocked-entity/index.html +++ b/navpatterns/1-patterns/blocked-entity/index.html @@ -6,16 +6,16 @@ Blocked Entity :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
      - - - + + +
    @@ -279,7 +279,7 @@ ALGuidelines.Dev

    The Blocked Entity is used when it is required to stop transactions for an entity (mostly master data), temporarily or permanently.

    Description

    -

    To block entities through metadata, read this pattern. To do the same thing through data, read Data Driven Blocked Entity pattern.

    +

    To block entities through metadata, read this pattern. To do the same thing through data, read Data Driven Blocked Entity pattern.

    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.

    @@ -320,7 +320,7 @@ ALGuidelines.Dev
  • Bin
-

The Released Entity.

+

The Released Entity.

@@ -336,12 +336,12 @@ ALGuidelines.Dev
- - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/cached-web-service-calls/index.html b/navpatterns/1-patterns/cached-web-service-calls/index.html index ff3e4d17..f4146eb0 100644 --- a/navpatterns/1-patterns/cached-web-service-calls/index.html +++ b/navpatterns/1-patterns/cached-web-service-calls/index.html @@ -5,16 +5,16 @@ Cached Web Server Calls :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
- - - + + +
    @@ -338,26 +338,28 @@ OnAction=VAR 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); +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); +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); @@ -378,12 +380,12 @@ OnAction=VAR
    - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/conditional-cascading-update/index.html b/navpatterns/1-patterns/conditional-cascading-update/index.html index 996c8334..4bac89b4 100644 --- a/navpatterns/1-patterns/conditional-cascading-update/index.html +++ b/navpatterns/1-patterns/conditional-cascading-update/index.html @@ -5,16 +5,16 @@ Conditional Cascading Update :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
- - - + + +
    @@ -288,12 +288,12 @@ ALGuidelines.Dev
    - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/copy-document/index.html b/navpatterns/1-patterns/copy-document/index.html index eee3c4fa..d3a64cf2 100644 --- a/navpatterns/1-patterns/copy-document/index.html +++ b/navpatterns/1-patterns/copy-document/index.html @@ -5,16 +5,16 @@ Copy Document :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
- - - + + +
    @@ -291,12 +291,20 @@ ALGuidelines.Dev

The Copy Document pattern involves the following entities:

    -
  1. Source document tables for document header and line. For example,Sales Header/Line.
  2. -
  3. Destination document tables for document header and line.
  4. +
  5. +

    Source document tables for document header and line. For example,Sales Header/Line.

    +
  6. +
  7. +

    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.

    +
  8. +
  9. +

    Copy Document engine: COD6620, Copy Document Mgt.

    +
  10. +
  11. +

    Copy Document report for a specific document type. The report requires the following parameters:

    +
-

**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.
  • @@ -319,7 +327,7 @@ ALGuidelines.Dev

    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.

    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.

    @@ -349,12 +357,12 @@ ALGuidelines.Dev
    - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/create-data-from-templates/index.html b/navpatterns/1-patterns/create-data-from-templates/index.html index dcd48d0f..6e9e47ad 100644 --- a/navpatterns/1-patterns/create-data-from-templates/index.html +++ b/navpatterns/1-patterns/create-data-from-templates/index.html @@ -5,16 +5,16 @@ Create Data from Templates :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
- - - + + +
    @@ -281,7 +281,7 @@ ALGuidelines.Dev

    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.

  • -

    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).

    +

    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:

    @@ -290,34 +290,41 @@ ALGuidelines.Dev
  • 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 theConfig. 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.

+

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.

  1. 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);
+
// 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); 
+
+// 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)
+
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)
+    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;
@@ -335,7 +342,7 @@ RecRef.SETTABLE(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).
  2. +
  3. 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.

@@ -350,8 +357,8 @@ RecRef.SETTABLE(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:

    +

    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
    • @@ -366,11 +373,10 @@ RecRef.SETTABLE(NAV Specific Example

      In C5 2014, this is the workflow:

      The user opens the Customers List window and selects New

      -

      ** **

      -

      +

      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.

      -

      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:

      +

      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:

      This pattern is used in Microsoft Dynamics C5 2014 in the following objects:

      @@ -428,12 +434,12 @@ RecRef.SETTABLE(
    - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/create-urls-to-nav-clients/index.html b/navpatterns/1-patterns/create-urls-to-nav-clients/index.html index f8d7cc11..33a01188 100644 --- a/navpatterns/1-patterns/create-urls-to-nav-clients/index.html +++ b/navpatterns/1-patterns/create-urls-to-nav-clients/index.html @@ -5,16 +5,16 @@ Create URLs to NAV Clients :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -301,218 +301,235 @@ GETURL(CLIENTTYPE

      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

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      CommandURL
      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 sessiondynamicsnav://MyServer:7046/DynamicsNAV71//
      GETURL(CLIENTTYPE::Default) ie. When the Server config key DefaultClient is set to Windowsdynamicsnav://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 publishedhttp://MyServer:7048/DynamicsNAV71/OData/Company(‘CRONUS’)/PAG27Vendors
      GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Query,9150) ie. When the Web Service is publishedhttp://MyServer:7048/DynamicsNAV71/OData/Company(‘CRONUS’)/QUE9150MyCustomers
      GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Page,27)Â ie. When the Web Service is publishedhttp://MyServer:7047/DynamicsNAV71/WS/CRONUS/Page/PAG27Vendors
      GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Codeunit,5065) ie. When the Web Service is publishedhttp://MyServer:7047/DynamicsNAV71/WS/CRONUS/Codeunit/COD5065EmailLogging
      GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,27,record) List Pagedynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=27&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw
      GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,26,record) Card Pagedynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=26&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw
      GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,27,record) List Pagehttps://navwebsrvr:443/DynamicsNAV71_Instance1/Webclient?company=CRONUS&page=27&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw
      GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,26,record) Card Pagehttps://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 CallError 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

      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:

      @@ -531,12 +548,12 @@ GETURL(CLIENTTYPE
    - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/creating-custom-charts/index.html b/navpatterns/1-patterns/creating-custom-charts/index.html index acce2b34..96737f3a 100644 --- a/navpatterns/1-patterns/creating-custom-charts/index.html +++ b/navpatterns/1-patterns/creating-custom-charts/index.html @@ -5,16 +5,16 @@ Creating Custom Charts :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -299,7 +299,7 @@ ALGuidelines.Dev
    • 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.

    +

    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:

    @@ -322,10 +322,10 @@ ALGuidelines.Dev

    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.
    • +
    • 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[

    +

    The relation between the components is visualized in the following diagram:

    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.

    @@ -339,24 +339,29 @@ ALGuidelines.Dev

    Implementation of the Finance Performance Chart page (762)

    BusinessChart::AddInReady()
    -    UpdateChart(Period::" ");
    +
    +UpdateChart(Period::" ");
     
     LOCAL UpdateChart(Period : ',Next,Previous')
    -    MoveAndUpdateChart(Period,0);
    +
    +MoveAndUpdateChart(Period,0);
     
     LOCAL MoveAndUpdateChart(Period : ',Next,Previous';Move : Integer)
    -    AccSchedChartManagement.GetSetupRecordset(AccountSchedulesChartSetup,AccountSchedulesChartSetup.Name,Move);
    +
    +AccSchedChartManagement.GetSetupRecordset(AccountSchedulesChartSetup,AccountSchedulesChartSetup.Name,Move);
     
    -    AccSchedChartManagement.UpdateData(Rec,Period,AccountSchedulesChartSetup);
    +AccSchedChartManagement.UpdateData(Rec,Period,AccountSchedulesChartSetup);
     
    -    Update(CurrPage.BusinessChart);
    +Update(CurrPage.BusinessChart);
     
    -    StatusText := GetCurrentSelectionText("Period Filter Start Date","Period Filter End Date");
    +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(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.

    @@ -408,12 +413,12 @@ ALGuidelines.Dev
    - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/cross-session-events/index.html b/navpatterns/1-patterns/cross-session-events/index.html index 1b61564e..cb9e4961 100644 --- a/navpatterns/1-patterns/cross-session-events/index.html +++ b/navpatterns/1-patterns/cross-session-events/index.html @@ -5,16 +5,16 @@ Cross Session Events :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -280,100 +280,163 @@ ALGuidelines.Dev
    • 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 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).

    +

    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 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.

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    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.”IntegerAutoIncrement
    “Type of Change”OptionInsert,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;
    +
    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;
    +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;
    +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;
    +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;
    +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;
    -    END;
    +
    +    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”.

    @@ -393,12 +456,12 @@ ALGuidelines.Dev
    - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/currently-active-record/index.html b/navpatterns/1-patterns/currently-active-record/index.html index 291f136f..77c14466 100644 --- a/navpatterns/1-patterns/currently-active-record/index.html +++ b/navpatterns/1-patterns/currently-active-record/index.html @@ -5,16 +5,16 @@ Currently Active Record :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -249,11 +249,8 @@ ALGuidelines.Dev
      @@ -308,7 +308,7 @@ ALGuidelines.Dev
    • Click Finish to close the wizard

    Then simply add your subpage as new line in the designer, and adjust the property “SubFormPerLink” with “Document No.=FIELD(No.)” to link header and lines :

    -

    ** **

    +

    Page “Document List”: Use the Page wizard to create a List page based on the Document table and add fields, FactBox (RecordLinks, Notes…), etc.

    Once created:

      @@ -348,12 +348,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/easy-update-of-setup-or-supplementary-information/index.html b/navpatterns/1-patterns/easy-update-of-setup-or-supplementary-information/index.html index 69ac14ae..89b54bf7 100644 --- a/navpatterns/1-patterns/easy-update-of-setup-or-supplementary-information/index.html +++ b/navpatterns/1-patterns/easy-update-of-setup-or-supplementary-information/index.html @@ -5,16 +5,16 @@ Easy Update Of Setup Or Supplementary Information :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -271,37 +271,34 @@ ALGuidelines.Dev

      Usage

      Define two functions in the setup or supplementary table: One for verifying if the needed information is available, and another for exposing the page that contains the fields that the user must update.

      Call the code. For example:

      -
      Local IsXAvailable : Boolean   
      -If field X <\> '' then   
      -Exit(True)  
      -Exit(false)  
      -  
      -VerifyAndSetX  
      -If IsXAvailable then  
      -Exit;  
      -If Confirm('Field X is missing a value. Do you want to update it now?') then  
      -Open the card page in edit mode  
      +
      Local IsXAvailable : Boolean   
      +If field X <> '' then   
      +Exit(True)  
      +Exit(false)  
         
      -If not IsXAvailable then  
      -Error(Field X is missing a value. Please correct it.)
      -
      -The calling code
      +VerifyAndSetX  
      +If IsXAvailable then  
      +Exit;  
      +If Confirm('Field X is missing a value. Do you want to update it now?') then  
      +Open the card page in edit mode  
      +  
      +If not IsXAvailable then  
      +Error(Field X is missing a value. Please correct it.)
      +

      The calling code

      +
      ..
       
      -..
      -
      -SetupTable.VerifyAndSetX
      +SetupTable.VerifyAndSetX
       
      -..
      -
      -
      +.. +

      In the Sales & Receivables Setup table (311) for the DK version, the following procedures have been added:

      -

      Local Procedure IsOIOUBLPathAvailable(…)

      -

      Procedure VerifyAndSetOIOUBLPath(…)

      -

      The code in the Sales & Receivables Setup table can now be called directly from the related processing codeunit, such as the Sales-Post + Print codeunit (82).

      +
      Local Procedure IsOIOUBLPathAvailable(...)
      +
      +Procedure VerifyAndSetOIOUBLPath(...)
      +

      The code in the Sales & Receivables Setup table can now be called directly from the related processing codeunit, such as the Sales-Post + Print codeunit (82).

      Were the code is called:

      -
      IF ("EAN No." <\> '') THEN
      -
      -SalesSetup.VerifyAndSetOIOUBLPathSetup(SalesHeader."Document Type");
      +
      IF ("EAN No." <> '') THEN
      +    SalesSetup.VerifyAndSetOIOUBLPathSetup(SalesHeader."Document Type");
       

      If the setup is not updated properly, the user is prompted to update it as follows.

      Choosing Yes opens the related setup page.

      @@ -332,12 +329,12 @@ SalesSetup.VerifyAndSetOIOUBLPathSetup
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/error-message-processing/index.html b/navpatterns/1-patterns/error-message-processing/index.html index 57ff0d7d..c8dfcf8b 100644 --- a/navpatterns/1-patterns/error-message-processing/index.html +++ b/navpatterns/1-patterns/error-message-processing/index.html @@ -5,16 +5,16 @@ Error Message Processing :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -263,7 +263,7 @@ ALGuidelines.Dev

      Error Message Processing

      By Jesper Schulz at Microsoft Development Center Copenhagen

      -

      Note: This pattern describes new functionality which makes it possible to generalize the Journal Error Processing pattern.

      +

      Note: This pattern describes new functionality which makes it possible to generalize the Journal Error Processing pattern.

      Abstract

      Missing, invalid or incomplete data is a common issue during data processing in NAV. This article describes how to collect all error messages during processing using the error message component and present them to the user in a unified way, which enables the user to correct the errors efficiently. By leveraging the integrated error message logging functions, you can log a message with a single line of code and present it to the user with another one-liner.

      Description

      @@ -275,7 +275,7 @@ ALGuidelines.Dev

      Validating data is a common task during data processing in NAV. Unfortunately, validation is often done using NAV’s integrated ERROR and TESTFIELD functions, which halt execution of the process. The user will then have to locate the invalid / missing data, correct it and reinitiate the process, possibly running into the next error, making the cycle repeat itself. This can be a very tedious, time-consuming and frustrating process. The error message component aims at improving this experience by providing a lightweight framework for error message logging and this article will explain how to leverage this functionality in your code. By doing so, all error messages are gathered during (pre-)processing and are finally presented to the user. The user then has the possibility to click on the error message, which will open the record where the invalid / missing data is located, thereby enabling the user to correct all mistakes efficiently, from one central place and in one go.

      The example below comes from a Mexican localization, where the user has to export financial balances and transactions into an XML file for government audit purposes. In order to generate valid files, some mandatory data needs to be entered in the system. By leveraging the error message component, the user will be presented with the following page, if missing or invalid data was discovered:

      -

      By clicking on the error message, the user will be presented with the entity, where the invalid / missing data should be corrected / added. This is done by applying the related pattern Easy Update of Setup or Supplementary Information.

      +

      By clicking on the error message, the user will be presented with the entity, where the invalid / missing data should be corrected / added. This is done by applying the related pattern Easy Update of Setup or Supplementary Information.

      Usage

      In the processing function, define a temporary record of type “Error Message”. Use the functions on that record to populate the record with error message, a few of them being:

      @@ -298,13 +298,14 @@ ALGuidelines.Dev

      When the processing is complete, you can check if any error messages of type “Error” were logged by calling the HasErrors function and you can show the list or error messages by calling the ShowErrorMessages function. You can also integrate the error messages list as a FactBox, but that is not part of this example.

      The code below is an example of how the error message component was used in one part of the before mentioned Mexican feature. This code iterates over all G/L Accounts and pipes information out into an XML file. While doing so, it is validated that all mandatory fields have values and meet certain conditions. And only if that is the case, is the XML document actually exported. Also notice, that an error message is logged, in case no G/L Accounts are found given the provided filters. That way, the user can be guided to setup the system correctly.

      -
          PROCEDURE ExportChartOfAccounts@1(Year@1000 : Integer;Month@1001 : Integer);
      -    VAR
      -        TempErrorMessage@1003 : TEMPORARY Record 700;
      -    BEGIN
      +
      PROCEDURE ExportChartOfAccounts@1(Year@1000 : Integer;Month@1001 : Integer);
      +VAR
      +    TempErrorMessage@1003 : TEMPORARY Record 700;
      +BEGIN
           TempErrorMessage.ClearLog; // only necessary if variable is global
           ...
           CreateXMLHeader(Document,RootNode,CatalogoNodeTxt,Namespace,Year,Month,'1.1');
      +
           IF GLAccount.FINDSET THEN BEGIN
               REPEAT
                   TempErrorMessage.LogIfEmpty (GLAccount,GLAccount.FIELDNO(Name),TempErrorMessage."Message Type"::Error);
      @@ -318,18 +319,18 @@ ALGuidelines.Dev
                       GLAccount."Debit/Credit"::Credit:
                           XMLDOMManagement.AddAttribute(Node,'Natur','A');
                   ELSE
      -            TempErrorMessage.LogMessage(
      -                GLAccount,GLAccount.FIELDNO("Debit/Credit"),TempErrorMessage."Message Type"::Error,
      -                STRSUBSTNO(GLAccountTypeErr,GLAccount."Debit/Credit",GLAccount.RECORDID));
      +                TempErrorMessage.LogMessage(
      +                    GLAccount,GLAccount.FIELDNO("Debit/Credit"),TempErrorMessage."Message Type"::Error,
      +                    STRSUBSTNO(GLAccountTypeErr,GLAccount."Debit/Credit",GLAccount.RECORDID));
                   END;
               UNTIL GLAccount.NEXT = 0;
           END ELSE
               TempErrorMessage.LogSimpleMessage(TempErrorMessage."Message Type"::Error,NoSATAccountDefinedErr);
      -    
      +
               IF NOT TempErrorMessage.HasErrors(TRUE) THEN
                   SaveXMLToClient(Document,Year,Month,'CT');
      -            TempErrorMessage.ShowErrorMessages(FALSE);
      -    END;
      +        TempErrorMessage.ShowErrorMessages(FALSE);
      +END;
       

      One could also do pre-processing in a function of its own, and only if the pre-processing results in no error messages of type “Error” would the processing continue.

      Ideas for improvement

      By using this easy to use component, we have the possibility to extend this functionality going forward. A nice addition to the error message component would be the possibility to log the error messages persistently in a grouped manner, thereby allowing 3rd parties to see the issues the users bump into the most, or allowing 3rd parties to get an detailed insight into what happened, thereby enabling them to provide better support.

      @@ -345,12 +346,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/extending-the-role-center-headlines/index.html b/navpatterns/1-patterns/extending-the-role-center-headlines/index.html index 9825b329..d94f3dbe 100644 --- a/navpatterns/1-patterns/extending-the-role-center-headlines/index.html +++ b/navpatterns/1-patterns/extending-the-role-center-headlines/index.html @@ -5,16 +5,16 @@ Extending the Role Center Headlines :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
      @@ -266,19 +266,14 @@ ALGuidelines.Dev

      Extending the Role Center Headlines

      By David Bastide at Microsoft Development Center Copenhagen

      -

      _ _

      -

      __

      +

      Context

      Headlines are designed as a page of type HeadlinePart containing at least one text field. The part is added to the top of Role Center pages.
      This document provides an elegant and extensible pattern about how to extend the Role Center headlines to add your own business headlines based on your data, and display them only if relevant.

      Description

      The Dynamics 365 Business Central release (April 2018) introduces a new HeadlinePart page type. This page type defines a page that rotates a display of several headlines after another, in the web client. A user can also click to switch to another headline. Headlines can also include a drilldown action that will be invoked when the user clicks the headline Text of the payload can be emphasized.
      -Headlines are divided in 2 parts: the qualifier, and the payload as you can see in the figure below.
      -_
      -_

      +Headlines are divided in 2 parts: the qualifier, and the payload as you can see in the figure below.

      -

      < –[if gte mso 9]>< [endif]–>< –[if gte mso 9]>< [endif]–>< –[if gte mso 9]>< [endif]–>< –[if gte mso 10]> -< [endif]–>

      Figure 1: Qualifier, Payload and emphasized text.

      Usage

      A page part has been added to each major Role Center:

      @@ -310,84 +305,79 @@ If no headline is added on these pages, fallback headlines will be displayed.

      Examples:

      1. Extending the page with a new headline:

      -
      group(LargestSale)  
      -{
      -Visible = LargestSaleVisible;
      -ShowCaption=false;
      -Editable=false;
      -  
      -field(LargestSaleText;LargestSaleText)
      -{
      -ApplicationArea = Basic, Suite;
      -DrillDown=true;
      -  
      -trigger OnDrillDown()
      -var
      -EssentialBusHeadlineMgt: Codeunit "Essential Bus. Headline Mgt.";
      -begin
      -EssentialBusHeadlineMgt.OnDrillDownLargestSale();
      -end;
      -}
      +
      group(LargestSale)  
      +{
      +    Visible = LargestSaleVisible;
      +    ShowCaption=false;
      +    Editable=false;
      +      
      +    field(LargestSaleText;LargestSaleText)
      +    {
      +        ApplicationArea = Basic, Suite;
      +        DrillDown=true;
      +        
      +        trigger OnDrillDown()
      +        var
      +            EssentialBusHeadlineMgt: Codeunit "Essential Bus. Headline Mgt.";
      +        begin
      +            EssentialBusHeadlineMgt.OnDrillDownLargestSale();
      +        end;
      +    }
       }  
      -
      -

      -

      2. Subscribing to the OnComputeHeadlines event, and computing headlines

      -
      \[EventSubscriber(ObjectType::Codeunit, Codeunit::"Headline RC Business Manager", 'OnComputeHeadlines', '', true, true)\]
      -procedure OnComputeHeadlinesBusinessManager()
      -begin
      -// \[...\] compute headline, and init the EssentialBusinessHeadline record
      -if not ShowHeadline then
      -exit; // not enough data to compute headline
      -if not HeadlineManagement.GetHeadlineText(
      -'Insight from last week',
      -StrSubstNo('The largest posted sales invoice was for %1',
      -HeadlineManagement.Emphasize(Format(CustomerLedgerEntry.Amount, 0, TypeHelper.GetAmountFormatWithUserLocale('$'))))
      -EssentialBusinessHeadline."Headline Text")
      -then
      -exit;
      -EssentialBusinessHeadline.Validate("Headline Visible", true);
      -EssentialBusinessHeadline.Modify();
      -end;  
      -
      -

      -

      3. Subscribing to the OnIsAnyExtensionHeadlineVisible event

      -
      \[EventSubscriber(ObjectType::Page, Page::"Headline RC Business Manager", 'OnIsAnyExtensionHeadlineVisible', '', true, true)\]
      -procedure OnIsAnyExtensionHeadlineVisible(var ExtensionHeadlinesVisible: Boolean)
      -var
      -EssentialBusinessHeadline: Record "Essential Business Headline";
      -AtLeastOneHeadlineVisible: Boolean;
      -begin
      -EssentialBusinessHeadline.SetRange("Headline Visible", true);
      -EssentialBusinessHeadline.SetFilter("Headline Name",'%1|%2|%3|%4|%5',
      -EssentialBusinessHeadline."Headline Name"::LargestOrder,
      -EssentialBusinessHeadline."Headline Name"::LargestSale,
      -EssentialBusinessHeadline."Headline Name"::BusiestResource,
      -EssentialBusinessHeadline."Headline Name"::MostPopularItem,
      -EssentialBusinessHeadline."Headline Name"::SalesIncrease,
      -EssentialBusinessHeadline."Headline Name"::TopCustomer);
      -AtLeastOneHeadlineVisible := not EssentialBusinessHeadline.IsEmpty();
      -// only modify the var if this extension is making some headlines visible, setting to false could override some other extensions setting the value to true
      -if AtLeastOneHeadlineVisible then
      -ExtensionHeadlinesVisible := true;
      -end;
      -
      -

      +

    2. Subscribing to the OnComputeHeadlines event, and computing headlines

    +
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Headline RC Business Manager", 'OnComputeHeadlines', '', true, true)]
    +    procedure OnComputeHeadlinesBusinessManager()
    +    begin
    +    // [...] compute headline, and init the EssentialBusinessHeadline record
    +    if not ShowHeadline then
    +        exit; // not enough data to compute headline
    +
    +    if not HeadlineManagement.GetHeadlineText(
    +        'Insight from last week',
    +        StrSubstNo('The largest posted sales invoice was for %1',
    +            HeadlineManagement.Emphasize(Format(CustomerLedgerEntry.Amount, 0, TypeHelper.GetAmountFormatWithUserLocale('$'))))
    +        EssentialBusinessHeadline."Headline Text")
    +    then
    +        exit;
    +
    +    EssentialBusinessHeadline.Validate("Headline Visible", true);
    +    EssentialBusinessHeadline.Modify();
    +end;
    +

    3. Subscribing to the OnIsAnyExtensionHeadlineVisible event

    +
    [EventSubscriber(ObjectType::Page, Page::"Headline RC Business Manager", 'OnIsAnyExtensionHeadlineVisible', '', true, true)]
    +procedure OnIsAnyExtensionHeadlineVisible(var ExtensionHeadlinesVisible: Boolean)
    +var
    +    EssentialBusinessHeadline: Record "Essential Business Headline";
    +    AtLeastOneHeadlineVisible: Boolean;
    +begin
    +    EssentialBusinessHeadline.SetRange("Headline Visible", true);
    +    EssentialBusinessHeadline.SetFilter("Headline Name",'%1|%2|%3|%4|%5',
    +        EssentialBusinessHeadline."Headline Name"::LargestOrder,
    +        EssentialBusinessHeadline."Headline Name"::LargestSale,
    +        EssentialBusinessHeadline."Headline Name"::BusiestResource,
    +        EssentialBusinessHeadline."Headline Name"::MostPopularItem,
    +        EssentialBusinessHeadline."Headline Name"::SalesIncrease,
    +        EssentialBusinessHeadline."Headline Name"::TopCustomer);
    +
    +    AtLeastOneHeadlineVisible := not EssentialBusinessHeadline.IsEmpty();
    +    // only modify the var if this extension is making some headlines visible, setting to false could override some other extensions setting the value to true
    +    if AtLeastOneHeadlineVisible then
    +        ExtensionHeadlinesVisible := true;
    +end;
    +

    4. Setting the headline text on the page

    -
    trigger OnAfterGetRecord()
    -begin
    -EssentialBusinessHeadline.GetHeadline(EssentialBusinessHeadline."Headline Name"::LargestSale);
    -LargestSaleVisible := EssentialBusinessHeadline."Headline Visible";
    -LargestSaleText := EssentialBusinessHeadline."Headline Text";
    -end;
    -
    -

    +
    trigger OnAfterGetRecord()
    +begin
    +    EssentialBusinessHeadline.GetHeadline(EssentialBusinessHeadline."Headline Name"::LargestSale);
    +    LargestSaleVisible := EssentialBusinessHeadline."Headline Visible";
    +    LargestSaleText := EssentialBusinessHeadline."Headline Text";
    +end;
    +

    Figure 2: Sequence diagram of headline usage

    Usages in NAV:

    • Essential Business Headlines extension
    -

    < –[if gte mso 9]>< [endif]–>< –[if gte mso 9]>< [endif]–>< –[if gte mso 9]>< [endif]–>< –[if supportAnnotations]–>< –[endif]–>< –[if gte mso 10]> -< [endif]–>

    @@ -400,12 +390,12 @@ end;
    - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/feature-localization-for-data-structures/index.html b/navpatterns/1-patterns/feature-localization-for-data-structures/index.html index bb0855d3..5f4acdf6 100644 --- a/navpatterns/1-patterns/feature-localization-for-data-structures/index.html +++ b/navpatterns/1-patterns/feature-localization-for-data-structures/index.html @@ -5,16 +5,16 @@ Feature Localization For Data Structures :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
      @@ -401,7 +401,7 @@ PrevVendorBillLine := VendorBillLine; VendorBillLine.TESTFIELD("Document Type",VendorBillLine."Document Type"::Invoice); -IF ((VendorBillLine."Vendor No." <\> PrevVendorBillLine."Vendor No.") OR (VendorBillLine."Vendor Bank Acc. No." <\> PrevVendorBillLine."Vendor Bank Acc. No.")) THEN BEGIN InsertTempGenJnlLine(TempGenJnlLine,VendorBillHeader,PrevVendorBillLine,CumulativeAmount); +IF ((VendorBillLine."Vendor No." <> PrevVendorBillLine."Vendor No.") OR (VendorBillLine."Vendor Bank Acc. No." <> PrevVendorBillLine."Vendor Bank Acc. No.")) THEN BEGIN InsertTempGenJnlLine(TempGenJnlLine,VendorBillHeader,PrevVendorBillLine,CumulativeAmount); CumulativeAmount := VendorBillLine."Amount to Pay"; @@ -501,12 +501,12 @@ INSERT;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/hooks/index.html b/navpatterns/1-patterns/hooks/index.html index 0902dfe1..732f758d 100644 --- a/navpatterns/1-patterns/hooks/index.html +++ b/navpatterns/1-patterns/hooks/index.html @@ -5,16 +5,16 @@ Hooks :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -312,25 +312,24 @@ ALGuidelines.Dev

      Consequences

      This pattern can be used in all cases to put business logic. But I see three possible approaches

        -
      1. You only declare the most important and most used hooks
      2. -
      +
    • You only declare the most important and most used hooks
      • This way, you limit the amount of objects and hooks to only a few in the default product
      • For example, only the OnBeforePostSalesHeader, OnBeforeReleaseSalesDocument, .. And no field validation or such…
      -
        -
      1. Only at objects of the default application which you are customizing.
      2. -
      +
    • +
    • Only at objects of the default application which you are customizing.
      • This way, you don’t want to create hooks for your own objects, only default existing objects.
      -
        -
      1. (recommended) You create hooks on all places you don’t want to write business logic, and on all existing objects which you would like to customize.
      2. -
      +
    • +
    • (recommended) You create hooks on all places you don’t want to write business logic, and on all existing objects which you would like to customize.
      • This is a very consistent way of working, as in any case, your business logic ends up in either a hook, or in its corresponding objects from a specific design pattern. But you know that the entry point is always a hook.
      • You know what to expect in any case, both changed business logic in existing code and business logic in newly created code is entered from a hook.
      +
    • +

      Atomic coding: It’s important that the hook function is readable in the most extreme way. For this, we recommend to use the “Atomic Coding” concept.

      See here a comparison / extension of hooks : http://www.waldo.be/2016/02/29/nav-2016-hooks-or-events/

      @@ -346,12 +345,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/implementation-of-surrogate-keys-using-autoincrement-pattern/index.html b/navpatterns/1-patterns/implementation-of-surrogate-keys-using-autoincrement-pattern/index.html index f3f5fdd5..192f388d 100644 --- a/navpatterns/1-patterns/implementation-of-surrogate-keys-using-autoincrement-pattern/index.html +++ b/navpatterns/1-patterns/implementation-of-surrogate-keys-using-autoincrement-pattern/index.html @@ -5,16 +5,16 @@ Surrogate keys using Autoincrement Pattern :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -248,14 +248,14 @@ ALGuidelines.Dev @@ -266,27 +266,24 @@ ALGuidelines.Dev

      Surrogate keys using Autoincrement Pattern

      By Soren Klemmensen, Partner-Ready-Software & 360 Visibility

      -

      Abstract

      +

      Abstract

      This Pattern is meant to create generic & reusable links between tables. The goal is to have an easy generic way to link a generically designed sub table to a record on a main table which can be used for other links too.

      To minimize the impact of customizations and to keep modules as generic and reusable as possible the idea of the Implementation of surrogate keys using AutoIncrement pattern is:

      • To create a generic and reusable auto generated link (A Surrogate Key), Immune to natural key data & requirement changes, on a main table with minimum impact on the table.
      • To create generic and reusable sub tables that effortless can be reused anywhere in the application.
      -

      Description

      +

      Description

      Over years of development many things are repeated across different implementation and even inside the same application. A typical example could be adding comments to an area just as it is done in Microsoft Dynamics NAV multiple times. There can be reasons for doing this again and again, but not only does this need to be maintained and upgraded over the years, but all the implementations of comments also needs to be tested separately. If a standard and generic comment could be developed and a generic way of connecting it to a main table this could be resolved. This is exactly what this pattern is trying accomplish.

      Figure 1: Table structure for linking a Document Header and Line Table with a Document Comment Table.

      -

      __

      -

      _ _

      +

      Figure 2: Table Structure for linking a Master Data Table with a Master Data Comment Table

      -

      __

      A typical way of linking a table to master data or to a document has been to use the primary key of the table being linked to. This causes some issues as the linked table now is designed specifically for the main table and it functionality cannot be reused. In case of renames the linked table needs to be renamed too which is costly in processing. Code also needs to be added on the delete trigger of the table to ensure that the attached records get removed if needed which increases the over all footprint of any change.

      -

      _ _

      +

      Figure 3: A Generic Way of creating a Comment table and linking it in a generic way to the main table no matter what this table might be. The Unique Record Identifier on the main tables is an Integer with AutoIncrement set to yes.

      -

      __

      It is recommended using this pattern in all tables which need sub tables unless specific reasons exists for not doing this.

      -

      Usage

      +

      Usage

      Step 1: Create a generic Unique Record Identifier in the main table

      The Pattern is implemented by adding a Field (I have called it Unique Record Identifier for this article) in a table (the main Table) where links are needed to be established to. Set the Property Data Type to Integer, Editable to No & AutoIncrement to Yes.

      Step 2: Create a generic link-able sub table.

      @@ -308,27 +305,20 @@ ALGuidelines.Dev

      Let’s assume we would like to create comment for a larger number of very different tables in the system.

      Update the Customer table (Step 1): Go to table 18 Customer and add Field 50000 Unique Record Identifier. Set the Property Data Type to Integer, Editable to No & AutoIncrement to Yes. Save the changes.

      -

      _ _

      -

      __

      -

      _ _

      -

      __

      +

      +

      _

      Create Comment table (Step 2): Create a new table called Comment. Add 3 fields Table No., Unique Record Identifier & Line No. all of Data Type Integer. Make these 3 fields into the primary key for the table. Add a 4 field called Comment with Data Type Text(80). Save the changes.

      Create the Comments page (Step 3): Start the page wizard and create a Page based on table comment created above as a List only showing the Comment field. Set AutoSplitKey property to Yes on the page.

      -

      __

      _ _

      -

      __

      Update the Customer Card (Step 4): Add an action to the Customer Card to open the Comments. Go to Actions and create an action called Smart Comment. Populate the properties RunObject, RunPageView and RunPageLink as see in the picture below.

      -

      __

      Create a hook OnDatabaseDelete (Step 5):

      Create a codeunit 50000 called “ApplicationManagement Hook” with one function called OnAfterOnDatabaseDelete taking the parameter RecRef of Data Type RecordRef. Add it as a global variable to Codeunit 1 ApplicationManagement and call the function as the last line in OnDatabaseDelete. Please read about the hook pattern before implementing it.

      -

      __

      Create the code needed to delete comments linked to a deleted record (Step6):

      Create a DeleteComments function taking the RecRef of Data Type RecordRef and add the code as shown blow.

      -

      __

      It is assumed in this example for simplicity that the Field 50000 is reserved across the entire application for the Unique Record Identifier as defined in Step 1.

      The comments are now fully working if we look away from the fact that we did not deal with a few things specific to Sales orders like RecreateSalesLines function, Archiving and Copy Document. All of which can easily be dealt with.

      These comments are now completely reusable everywhere else in the system. Sales Document is a perfect example as the primary keys of the Sales Header and the Sales line are both different from the customer and normally we would not be able to use the comments header or the line. All that needs to be done is adding the Field 50000 Unique Record Identifier to the Sales Header & Sales Line (Disregarding the posted documents in this example) and creating the actions on the Page Actions with the needed filters. Deletion is already handled of comments no matter what the main table might be.

      @@ -336,15 +326,14 @@ ALGuidelines.Dev

      Other ideas for use of this pattern could be. An Error table, Tags, Dynamically defined fields and Generic fact boxes. Only the imagination sets limits for its use.

      Upgrade wise there can be an impact if data is moved out of tables to be reinserted again because of a change to a database structure. This will cause the Unique Record Identifier to change, unless steps are taken to avoid this, and the links will need to be reestablished.

      Other risks could be if Transfer Fields are used and data is being copied unintentionally.

      - +

      This is a new pattern not currently used in Microsoft Dynamics NAV.

      -

      Ideas for improvement

      +

      Ideas for improvement

      The reason for doing it this way is that you now can reuse your code again and again with only creating the same link on other tables instead of building it from scratch each time reducing testing needed and improving the quality of the overall product.

      -

      Consequences (When it should not be used)

      +

      Consequences (When it should not be used)

      This can be used on any table when linking anything to it that can be considered a generic module which can be reused. That said it should not be used in cases where there is a risk of tables growing so much that performance could be impacted. There are ways to reduce performance impact when using high volume tables, but that is outside the scope of this pattern.

      - +

      This is related to the hook pattern as far as they both try to reduce the footprint changes have on the standard application, by creating reusable ways to interact with the standard code. That said the hook pattern is more about hooking the functionality to existing while this pattern is more about creating reusable ways of creating functionality.

      -

      __

      @@ -357,12 +346,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/index.html b/navpatterns/1-patterns/index.html index 3cde766b..c620518b 100644 --- a/navpatterns/1-patterns/index.html +++ b/navpatterns/1-patterns/index.html @@ -6,16 +6,16 @@ 1. Patterns :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -263,12 +263,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/index.xml b/navpatterns/1-patterns/index.xml index fb3f2a67..ea1aaf52 100644 --- a/navpatterns/1-patterns/index.xml +++ b/navpatterns/1-patterns/index.xml @@ -44,8 +44,6 @@ Abstract Users or the administrator must regularly update setup or supplementary The navigation experience around these extra steps is often quite troublesome and time-consuming. To enable users to easily perform the needed update, a guide often gives the best support.Error Message Processinghttps://alguidelines.dev/navpatterns/1-patterns/error-message-processing/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/error-message-processing/By Jesper Schulz at Microsoft Development Center Copenhagen Note: This pattern describes new functionality which makes it possible to generalize the Journal Error Processing pattern. Abstract Missing, invalid or incomplete data is a common issue during data processing in NAV. This article describes how to collect all error messages during processing using the error message component and present them to the user in a unified way, which enables the user to correct the errors efficiently.Extending the Role Center Headlineshttps://alguidelines.dev/navpatterns/1-patterns/extending-the-role-center-headlines/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/extending-the-role-center-headlines/By David Bastide at Microsoft Development Center Copenhagen -__ -__ Context Headlines are designed as a page of type HeadlinePart containing at least one text field. The part is added to the top of Role Center pages. This document provides an elegant and extensible pattern about how to extend the Role Center headlines to add your own business headlines based on your data, and display them only if relevant. Description The Dynamics 365 Business Central release (April 2018) introduces a new HeadlinePart page type.Feature Localization For Data Structureshttps://alguidelines.dev/navpatterns/1-patterns/feature-localization-for-data-structures/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/feature-localization-for-data-structures/Originally by Bogdan Sturzoiu at Microsoft Development Center Copenhagen @@ -66,13 +64,12 @@ Description This pattern creates a standard master data entity, listing all nece It is being used for creating a master data entity and can roughly be divided into 2 categories: Business and ProductMulti-file Downloadhttps://alguidelines.dev/navpatterns/1-patterns/multi-file-download/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/multi-file-download/By Martin Dam at Microsoft Development Center Copenhagen Abstract The goal of this pattern is to enable the users to download multiple files as a zip file instead of downloading one by one. On the Web Client this is preferred way of delivering multiple files since it is one of the web patterns and we cannot use File Management code unit to place files silently on the machine. Description When generating reports that consists of multiple, and usually an unknown number of files, the developer will have to handle the download which also depends on the client the user is on.Multi-Page Listhttps://alguidelines.dev/navpatterns/1-patterns/multi-page-list/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/multi-page-list/By Bogdana Botez at Microsoft Development Center Copenhagen -__ Abstract This pattern describes how to open a related document (or card) page from a list page, for the case when there can be more than one pages associated to the rows of the list page. Description The example below illustrates the connection of a List Page with multiple Document Pages, while the second example links the List Page with Card Pages.Multilanguage Application Datahttps://alguidelines.dev/navpatterns/1-patterns/multilanguage-application-data/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/multilanguage-application-data/Originally by Bogdana Botez at Microsoft Development Center Copenhagen Abstract Generally, NAV translation refers to the translation of UI elements like captions and user texts (messages on dialogs, warnings, error messages). This translation is done by the Microsoft Dynamics NAV team before releasing the localized version of the product. But there is one more scenario. In this scenario, Cronus International Ltd., wants to sell a &ldquo;Fiets&rdquo; to a Dutch customer, a&quot;Cykel&quot; to a Danish one, and a &ldquo;Bicicletta&rdquo; to an Italian customer.No Serieshttps://alguidelines.dev/navpatterns/1-patterns/no-series/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/no-series/By Bogdana Botez, at Microsoft Development Center Copenhagen Abstract Number series assignment is widely used in Microsoft Dynamics NAV to automatically assign unique numbers to data entries. -Description Documents and entities of any type (invoices, orders, customers, inventory items etc) are usually assigned an unique number, which can be later referenced whenever that particular entry needs to be consulted. When a new data entry is created, NAV implements the possibility to auto-assign a new number for this entry.Observerhttps://alguidelines.dev/navpatterns/1-patterns/observer/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/observer/By Nikolai L&rsquo;Estrange, from TVision Technology Ltd. in the UK _ +Description Documents and entities of any type (invoices, orders, customers, inventory items etc) are usually assigned an unique number, which can be later referenced whenever that particular entry needs to be consulted. When a new data entry is created, NAV implements the possibility to auto-assign a new number for this entry.Observerhttps://alguidelines.dev/navpatterns/1-patterns/observer/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/observer/By Nikolai L&rsquo;Estrange, from TVision Technology Ltd. in the UK Abstract Track all record changes against a defined table or set of tables. Problem Microsoft Dynamics NAV has the built in ability to track all record changes against any table, however it does not always fire the triggers for every table. Solution Create a setup table to define which other tables you want to track changes for, and optionally what triggers you want to fire, then link this up to the standard triggers in Codeunit 1.Posting Routine - Select Behaviorhttps://alguidelines.dev/navpatterns/1-patterns/posting-routine-select-behavior/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/posting-routine-select-behavior/By waldo diff --git a/navpatterns/1-patterns/instructions-in-the-ui/index.html b/navpatterns/1-patterns/instructions-in-the-ui/index.html index 1bcf0493..95bbb435 100644 --- a/navpatterns/1-patterns/instructions-in-the-ui/index.html +++ b/navpatterns/1-patterns/instructions-in-the-ui/index.html @@ -5,16 +5,16 @@ Instructions in the UI :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -315,16 +315,14 @@ ALGuidelines.Dev

      Important parts:
      VideoPlayerAddIn.SetFrameAttribute function is used to set an attribute to the iframe that will be playing the video.

      Example of the embed code:

      -

      You must assign src attribute to src of the embed code, for example:

      -
      VideoPlayer.SetFrameAttribute('src', 'https://www.youtube.com/embed/7SGp9pA9cAY');
      -
      -

      Without this, the video will not play. You can use the same function to assign other attributes, for example to remove frame border use:

      -
      VideoPlayer.SetFrameAttribute(' frameborder', '0');
      -
      -

      Height and width should be set by using the following functions, since they ensure that the video will be centered on the page.

      -
      VideoPlayerAddIn.SetHeight(Height) and VideoPlayerAddIn.SetWidth(Width);
      -
      -

      If you would like to reuse the Mini Video Player Page page (1395), then use:

      +
      <iframe width="560" height="315" src="//www.youtube.com/embed/7SGp9pA9cAY" frameborder="0" allowfullscreen\></iframe\>
      +

      You must assign src attribute to src of the embed code, for example:

      +
          VideoPlayer.SetFrameAttribute('src', 'https://www.youtube.com/embed/7SGp9pA9cAY');
      +

      Without this, the video will not play. You can use the same function to assign other attributes, for example to remove frame border use:

      +
          VideoPlayer.SetFrameAttribute(' frameborder', '0');
      +

      Height and width should be set by using the following functions, since they ensure that the video will be centered on the page.

      +
          VideoPlayerAddIn.SetHeight(Height) and VideoPlayerAddIn.SetWidth(Width);
      +

      If you would like to reuse the Mini Video Player Page page (1395), then use:

      SetParameters(Height,Width,Src,Caption), which uses the functions described above.
       

      4. As a last step you need to implement the action on the group and assign a video icon

      @@ -364,12 +362,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/integration-of-addresses/index.html b/navpatterns/1-patterns/integration-of-addresses/index.html index 5f316675..3ac5c5a0 100644 --- a/navpatterns/1-patterns/integration-of-addresses/index.html +++ b/navpatterns/1-patterns/integration-of-addresses/index.html @@ -5,16 +5,16 @@ Integration of Addresses :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -269,12 +269,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/journal-error-processing/index.html b/navpatterns/1-patterns/journal-error-processing/index.html index 2130b3b9..b007af2c 100644 --- a/navpatterns/1-patterns/journal-error-processing/index.html +++ b/navpatterns/1-patterns/journal-error-processing/index.html @@ -5,16 +5,16 @@ Journal Error Processing :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -287,47 +287,60 @@ ALGuidelines.Dev

      Application Objects

      In the following table, the Generic Object column contains the objects that you can use as a base for your implementation.

      -

      Generic Object

      -

      Description

      -

      Sample W1 implementation of SEPA Credit Transfer*

      -

      Journal Page

      -

      This is the journal list page where the user invokes the processing action.

      -

      Payment Journal

      -

      Action on Page

      -

      The processing action invoked by the user on the journal list page.

      -

      Export Payments to File

      -

      Errors Page List Part

      -

      A FactBox that displays any journal line validation errors.

      -

      To improve user experience, the developer can highlight the lines with errors in red and conveniently sort the lines with errors at the top.

      -

      Payment Journal Errors Part

      -

      Validation codeunit

      -

      Contains code that checks that the journal line contains correct, complete, and coherent data and that the line is ready for whatever process must be done next.

      -

      SEPA CT-Check Line

      -

      Processing codeunit

      -

      Executes the processing of the journal lines.

      -

      SEPA CT-Export File

      -

      Journal Error Text Table

      -

      Contains

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Generic ObjectDescriptionSample W1 implementation of SEPA Credit Transfer
      Journal PageThis is the journal list page where the user invokes the processing action.Payment Journal
      Action on PageThe processing action invoked by the user on the journal list page.Export Payments to File
      Errors Page List PartA FactBox that displays any journal line validation errors.
      To improve user experience, the developer can highlight the lines with errors in red and conveniently sort the lines with errors at the top.Payment Journal Errors Part
      Validation codeunitContains code that checks that the journal line contains correct, complete, and coherent data and that the line is ready for whatever process must be done next.SEPA CT-Check Line
      Processing codeunitExecutes the processing of the journal lines.SEPA CT-Export File
      +

      Generic Object: Journal Error Text Table

      +

      Description:

      +

      Contains:

        -
      • -

        The error messages

        -
      • -
      • -

        Link information about where the error messages belong. For example, in table 1228, Payment Jnl. Export Error Text, the error is linked uniquely to a journal line by the following fields:

        -
      • -
      • -

        Journal Template Name, with TableRelation=“Gen. Journal Template”

        -
      • -
      • -

        Journal Batch Name, with TableRelation=“Gen. Journal Batch”.Name WHERE (Journal Template Name=FIELD(Journal Template Name))

        -
      • -
      • -

        Journal Line No.

        -
      • +
      • The error messages
      • +
      • Link information about where the error messages belong. For example, in table 1228, Payment Jnl. Export Error Text, the error is linked uniquely to a journal line by the following fields:
      • +
      • Journal Template Name, with TableRelation=“Gen. Journal Template”
      • +
      • Journal Batch Name, with TableRelation=“Gen. Journal Batch”.Name WHERE (Journal Template Name=FIELD(Journal Template Name))
      • +
      • Journal Line No.

      Other related information can be added, such as document number of the original source document, if the current journal line originates from a document.

      An extra improvement would be to add a drilldown or a link to the page where the user can fix the error. This would significantly simplify the scenario by excluding manual navigation and investigation by the user to find the page where the error can be fixed.

      -

      Payment Jnl. Export Error Text

      +

      Sample W1 implementation of SEPA Credit Transfer: Payment Jnl. Export Error Text

      * The W1 implementation of file export for SEPA Credit Transfer contains the generic SEPA functionality. However, due to differences in data models and user scenarios in various country implementations, the selected local versions contain adaptations of the generic functionality.

      Flow

      Find below a diagram describing the flow between the objects involved in the journal error processing.

      @@ -412,12 +425,12 @@ DELETEALL;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/journal-template-batch-line/index.html b/navpatterns/1-patterns/journal-template-batch-line/index.html index 85c11bcd..3b2dec40 100644 --- a/navpatterns/1-patterns/journal-template-batch-line/index.html +++ b/navpatterns/1-patterns/journal-template-batch-line/index.html @@ -5,16 +5,16 @@ Journal Template Batch Line :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -282,7 +282,7 @@ ALGuidelines.Dev

      Abstract

      The role of a journal line is to temporarily hold transaction data until the transaction is posted. Before posting, the entries are in a draft state, which means that they are available for corrections and/or deletion. As soon as the entries are posted, they are converted to ledger entries.

      Journal templates are used to specify the underlying journal structure and to provide the default information for the journal batches. Journal batches usually serve to group journal lines, such as lines created by two different users.

      -

      _ _

      +

      Description

      Journal templates and journal batches are used if there is a need to create and post one or more entries. They are implemented in multiple areas of the application, like Sales, Purchases, Cash Receipts, Payments, Fixed Assets1.

      Journal Templates

      @@ -295,19 +295,36 @@ ALGuidelines.Dev
    • No. Series

    The Journal Template table stores the relevant attributes that define the nature and behavior of the journal templates, for example:

    -

    Journal Template Table Field

    -

    Description

    -

    Test Report ID

    -

    The journals offer the possibility of running test reports3. The role of a test report is to simulate the posting process. The verification criteria for the journal lines is ran, and the report can be displayed, all without doing the actual posting. This helps finding and correcting any errors that might exist in the data.

    -

    The name of the test report is the same with the name of the corresponding journal, plus the suffix " - Test". For example, the General Journal has the associated test report named General Journal - Test.

    -

    Posting Report ID

    -

    This report is printed when a user selects Post and Print4.

    -

    Page ID

    -

    For some journals, more UI objects are required. For example, the General Journals have a special page for bank and cash.

    -

    Source Code

    -

    Here you can enter a Trail Code for all the postings done through this Journal4.

    -

    Recurring

    -

    Whenever you post lines from a recurring journal, new lines are automatically created with a posting date defined in the recurring date formula.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Journal Template Table FieldDescription
    Test Report IDThe journals offer the possibility of running test reports3. The role of a test report is to simulate the posting process. The verification criteria for the journal lines is ran, and the report can be displayed, all without doing the actual posting. This helps finding and correcting any errors that might exist in the data. The name of the test report is the same with the name of the corresponding journal, plus the suffix " - Test". For example, the General Journal has the associated test report named General Journal - Test.
    Posting Report IDThis report is printed when a user selects Post and Print4.
    Page IDFor some journals, more UI objects are required. For example, the General Journals have a special page for bank and cash.
    Source CodeHere you can enter a Trail Code for all the postings done through this Journal4.
    RecurringWhenever you post lines from a recurring journal, new lines are automatically created with a posting date defined in the recurring date formula.

    Each journal template defines a default value of those attributes. The values that are defined in a template will be inherited by the journal batches, which will be created from a journal template.

    Microsoft Dynamics NAV is released with a number of standard journal templates predefined in the Journal Templates page. More templates can be defined by the users.

    Journal Batches

    @@ -317,7 +334,7 @@ ALGuidelines.Dev

    Journal lines contain the actual business data (posting dates, account numbers, amounts) that will be posted as ledger entries.

    During posting, only the information from the journal lines is needed. However, the information has been created with the help of the journal templates and grouped together using the journal batches.

    Posting creates ledger entries from the temporary content that is stored in the journal lines. Ledger entries are not created directly. Instead, they are posted from journal lines.

    -

    _ _

    +

    Aggregation

    There is a 1:n aggregation relationship between journal templates and journal batches, as well as between journal batches and journal lines. Deleting a template will cascade deletion of the related batches and lines. Deleting a batch will cascade into deletion of related lines.

    Recurring Journals

    @@ -359,28 +376,25 @@ ALGuidelines.Dev

    The General Journals Batches page (251) is linked to the Gen. Journal Batch source table (232), which has a multiple-to-1 relationship with Gen. Journal Template table, based on the Journal Template Name field.

    Some of the fields in the Gen. Journal Batch table are not editable. Instead, their value is automatically calculated from the parent Gen. Journal Template table. For example, the Recurring field (22) is a FlowField with the following calculation formula:

    -
    Lookup("Gen. Journal Template".Recurring WHERE (Name=FIELD(Journal Template Name)))
    -
    -

    Similarly, the Template Type field is a FlowField that gets its value from the parent table:

    -
    Lookup("Gen. Journal Template".Type WHERE (Name=FIELD(Journal Template Name)))
    -
    -

    Setting up a New Batch

    +
    Lookup("Gen. Journal Template".Recurring WHERE (Name=FIELD(Journal Template Name)))
    +

    Similarly, the Template Type field is a FlowField that gets its value from the parent table:

    +
    Lookup("Gen. Journal Template".Type WHERE (Name=FIELD(Journal Template Name)))
    +

    Setting up a New Batch

    When the user creates a new batch, the following field values are transferred from the Gen. Journal Template table to the Gen. Journal Batch table:

    -
    "Bal. Account Type" := GenJnlTemplate."Bal. Account Type";
    +
    "Bal. Account Type" := GenJnlTemplate."Bal. Account Type";
     
    -"Bal. Account No." := GenJnlTemplate."Bal. Account No.";
    +"Bal. Account No." := GenJnlTemplate."Bal. Account No.";
     
    -"No. Series" := GenJnlTemplate."No. Series";
    +"No. Series" := GenJnlTemplate."No. Series";
     
    -"Posting No. Series" := GenJnlTemplate."Posting No. Series";
    +"Posting No. Series" := GenJnlTemplate."Posting No. Series";
     
    -"Reason Code" := GenJnlTemplate."Reason Code";
    +"Reason Code" := GenJnlTemplate."Reason Code";
     
    -"Copy VAT Setup to Jnl. Lines" := GenJnlTemplate."Copy VAT Setup to Jnl. Lines";
    +"Copy VAT Setup to Jnl. Lines" := GenJnlTemplate."Copy VAT Setup to Jnl. Lines";
     
    -"Allow VAT Difference" := GenJnlTemplate."Allow VAT Difference";
    -
    -

    Cascade record deletion

    +"Allow VAT Difference" := GenJnlTemplate."Allow VAT Difference"; +

    Cascade record deletion

    When a record from the Gen. Journal Template table is deleted, the corresponding Gen. Journal Batch and Gen. Journal Line records are also deleted.

    Cascade updates

    When the reason code or the posting number series change in the current batch, all linked Gen. Journal Line records are updated (see ModifyLines function on the Gen. Journal Batch table).

    @@ -418,12 +432,12 @@ ALGuidelines.Dev
    - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/master-data/index.html b/navpatterns/1-patterns/master-data/index.html index 270e0316..8bcce040 100644 --- a/navpatterns/1-patterns/master-data/index.html +++ b/navpatterns/1-patterns/master-data/index.html @@ -5,16 +5,16 @@ Master Data :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
      @@ -262,53 +262,118 @@ ALGuidelines.Dev

      It is being used for creating a master data entity and can roughly be divided into 2 categories: Business and Product

      Examples of Master Data tables are:

      Business Data:

      -

      Table 18: Customer

      -

      Table 23: Vendor

      -

      Table 270: Bank Account

      -

      Table 5050: Contact

      +
        +
      • Table 18: Customer
      • +
      • Table 23: Vendor
      • +
      • Table 270: Bank Account
      • +
      • Table 5050: Contact
      • +

      Product Data:

      -

      Table 15 G/L Account

      -

      Table 27: Item

      -

      Table 156: Resource

      -

      Table 5600: Fixed Asset

      -

      Table 5718: Nonstock Item

      -

      Table 5913: Loaner

      +
        +
      • Table 15 G/L Account
      • +
      • Table 27: Item
      • +
      • Table 156: Resource
      • +
      • Table 5600: Fixed Asset
      • +
      • Table 5718: Nonstock Item
      • +
      • Table 5913: Loaner
      • +

      Master Data has attributes such as Location, Countries & Item Vendors. These tables are closely related to Master Data tables and are called Supplemental and Subsidiary tables.

      Supplemental:

      -

      Table 9: Country/Region

      -

      Table 14: Location

      +
        +
      • Table 9: Country/Region
      • +
      • Table 14: Location
      • +

      Subsidiary:

      -

      Table 99: Item Vendor

      +
        +
      • Table 99: Item Vendor
      • +

      Master Data is among other used in journals, posting routines and historic data like ledger entries.

      Naming & Conventions

      -

      Table & Card Page

      +
        +
      • Singular

        +
      • +
      • Examples: Customer, Vendor & Item

        +
      • +

      List Page

      +
        +
      • Plural if Editable = TRUE.

        +
      • +
      • Singular + “List” if Editable = FALSE.

        +
      • +

      Example

      The data entity has a single primary key field with the following properties:

      -

      Field No.

      -

      1

      -

      Name

      -

      “No.”

      -

      Date Type

      -

      Code 20

      + + + + + + + + + + + + + + + + + + + + + +
      Field No.1
      Name“No.”
      Date TypeCode 20

      Each master data entity has a field which gives a name tag to the data entity carrying the natural name of the entity. This field is called Name if the data entity refers to a living person or an organization, such as a customer or a vendor; it is called Description instead if the data entity does not refer to a person or organization, such as an item. The field has the following properties:

      -

      Name

      -

      Name or Description

      -

      Date Type

      -

      Text 50

      + + + + + + + + + + + + + + + + + +
      NameName or Description
      Date TypeText 50

      An added benefit of using the Name or Description field naming is that this will be part of the dropdown when looking up based on the table relation.

      The table properties of a master data table contain the following entries:

      -

      LookupPageID

      -

      A ListType Page referring to the table which is not editable.

      -

      DrillDownPageID

      -

      The same page as defined in the LookupPageID

      -

      DataCaptionFields

      -

      The primary key plus the field which provides the primary name tag for the record. This will typically be the Name or Description field defined earlier.

      + + + + + + + + + + + + + + + + + + + + + +
      LookupPageIDA ListType Page referring to the table which is not editable.
      DrillDownPageIDThe same page as defined in the LookupPageID
      DataCaptionFieldsThe primary key plus the field which provides the primary name tag for the record. This will typically be the Name or Description field defined earlier.

      NAV Specific Example

      We will create an Example table here with a Card and List Page as described above.

      First we create the Table with No. as the primary key.

      @@ -327,17 +392,21 @@ ALGuidelines.Dev

      NAV Usages

      You can see this pattern used in the following tables & pages:

      Business Data:

      -

      Table 18: Customer, Page 21 Customer Card & Page 22 Customer List

      -

      Table 23: Vendor, Page 26 Vendor Card & Page 27 Vendor List

      -

      Table 270: Bank Account, Page 370 Bank Account Card & Page 371 Bank Account List

      -

      Table 5050: Contact, Page 5050 Contact Card & Page 5052 Contact List

      +
        +
      • Table 18: Customer, Page 21 Customer Card & Page 22 Customer List
      • +
      • Table 23: Vendor, Page 26 Vendor Card & Page 27 Vendor List
      • +
      • Table 270: Bank Account, Page 370 Bank Account Card & Page 371 Bank Account List
      • +
      • Table 5050: Contact, Page 5050 Contact Card & Page 5052 Contact List
      • +

      Product Data:

      -

      Table 15 G/L Account, Page 17 G/L Account Card & Page 18 G/L Account List

      -

      Table 27: Item, Page 30 Item Card & Page 31 Item List

      -

      Table 156: Resource, Page 76 Resource Card & Page 77 Resource List

      -

      Table 5600: Fixed Asset, Page 5600 Fixed Asset Card & Page 5601 Fixed Asset List

      -

      Table 5718: Nonstock Item, Page 5725 Nonstock Item Card & Page 5726 Nonstock Item List

      -

      Table 5913: Loaner, Page 5922 Loaner Card & Page 5923 Loaner List

      +
        +
      • Table 15 G/L Account, Page 17 G/L Account Card & Page 18 G/L Account List
      • +
      • Table 27: Item, Page 30 Item Card & Page 31 Item List
      • +
      • Table 156: Resource, Page 76 Resource Card & Page 77 Resource List
      • +
      • Table 5600: Fixed Asset, Page 5600 Fixed Asset Card & Page 5601 Fixed Asset List
      • +
      • Table 5718: Nonstock Item, Page 5725 Nonstock Item Card & Page 5726 Nonstock Item List
      • +
      • Table 5913: Loaner, Page 5922 Loaner Card & Page 5923 Loaner List
      • +

      Consequences

      This pattern should not be used when this is not Master Data.

      References

      @@ -354,12 +423,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/multi-file-download/index.html b/navpatterns/1-patterns/multi-file-download/index.html index f22982b9..33e1d3f4 100644 --- a/navpatterns/1-patterns/multi-file-download/index.html +++ b/navpatterns/1-patterns/multi-file-download/index.html @@ -5,16 +5,16 @@ Multi-file Download :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -280,39 +280,37 @@ ALGuidelines.Dev

      For first step consists of a loop that goes through the files that needs to be downloaded. If it is on the Web client, the files are added to a ZIP archive server-side using a naming convention defined by the integration function GetSeriesFileName. This function takes a filename and number, and transforms it to unique names following a meaningful deterministic pattern e.g. prepend an integer before the file extension. The same function is used when the temporary files are created server side, so the files can be found deterministically later. This removes the need for storing filenames and consequently allows an arbitrary number of files. The second integration function: GetTotalNumberOfFiles, returns the total number of files generated during the data processing and makes the pattern able to handle an arbitrary number of files.

      The second step is the actually download of file(s). For the Web client this consists of closing the ZIP achieve and downloading via the standard download handler that works in the Web client. For the Win client, the files are saved directly to the client during the first step.

      Code 1: File loop shows an example implementation of this pattern. ServerFileName is generated at the beginning of the report/codeunit, and is the base for GetSeriesFilename. The file that is actually written to during data processing is stored in another variable which holds the output from GetSeriesFilename on the current file number. Note; the example code will only create a ZIP file if there in fact are multiple files to be downloaded.

      -
      IF FileName = '' THEN
      -ERROR(SupplyFileNameErr);
      -IF ZipMultipleFiles AND (GetTotalNumberOfFiles \> 1) AND FileManagement.IsWebClient
      -THEN BEGIN
      -Basename := FileManagement.GetFileName(FileName);
      -ZipFileName := FileManagement.CreateZipArchiveObject;
      -FOR FileNo := 1 TO GetTotalNumberOfFiles DO
      -FileManagement.AddFileToZipArchive(
      -GetSeriesFilename(ServerFileName,FileNo),GetSeriesFilename(Basename,FileNo));
      -FileManagement.DownloadHandler(ZipFileName,'','','',
      -STRSUBSTNO('%1.zip',FileManagement.GetFileNameWithoutExtension(FileName)))
      -END ELSE BEGIN
      -IF FileManagement.IsWebClient THEN BEGIN
      -IF GetTotalNumberOfFile \> 1 THEN
      -ERROR(MultipleFilesWebClientErr);
      -FileManagementDownloadHandler(GetSeriesFilename(ServerFileName,1),'','','',
      -FileManagement.GetFileName(FileName));
      -END ELSE
      -FOR FileNo := 1 TO GetTotalNumberOfFiles DO
      -FileManagement.DownloadToFile(GetSeriesFilieName(ServerFileName,FileNo),
      -GetSeriesFilename(FileName,FileNo));
      -END; 
      -
      -

      Code 1: File loop

      -

      __

      +
      IF FileName = '' THEN
      +    ERROR(SupplyFileNameErr);
      +
      +IF ZipMultipleFiles AND (GetTotalNumberOfFiles \> 1) AND FileManagement.IsWebClient
      +THEN BEGIN
      +    Basename := FileManagement.GetFileName(FileName);
      +    ZipFileName := FileManagement.CreateZipArchiveObject;
      +    FOR FileNo := 1 TO GetTotalNumberOfFiles DO
      +        FileManagement.AddFileToZipArchive(
      +            GetSeriesFilename(ServerFileName,FileNo),GetSeriesFilename(Basename,FileNo));
      +    FileManagement.DownloadHandler(ZipFileName,'','','',
      +        STRSUBSTNO('%1.zip',FileManagement.GetFileNameWithoutExtension(FileName)))
      +END ELSE BEGIN
      +    IF FileManagement.IsWebClient THEN BEGIN
      +        IF GetTotalNumberOfFile > 1 THEN
      +            ERROR(MultipleFilesWebClientErr);
      +        FileManagementDownloadHandler(GetSeriesFilename(ServerFileName,1),'','','',
      +            FileManagement.GetFileName(FileName));
      +    END ELSE
      +        FOR FileNo := 1 TO GetTotalNumberOfFiles DO
      +            FileManagement.DownloadToFile(GetSeriesFilieName(ServerFileName,FileNo),
      +                GetSeriesFilename(FileName,FileNo));
      +END; 
      +

      Code 1: File loop

      The following code is an example implementation of the GetSeriesFilename function. It needs to support the case where no directory is given, only a filename, in order to add files to the root of the ZIP archive. The example will add a file number right before the extension, e.g. C:\directory\file.txt will become C:\directory\file1.txt etc.

      -
      LOCAL GetSeriesFilename(FileName : Text;FileNo : Integer) : Text
      -IF STRPOS(FileName,'\\') <\> 0 THEN
      -Directory := FileMgt.GetDirectoryName(FileName) + '\\';
      -EXIT(Directory + FileMgt.GetFileNameWithoutExtension(FileName) + FORMAT(FileNo) + '.' +   
      -FileMgt.GetExtension(FileName));
      -
      -

      Code 2: GetSeriesFilename

      +
      LOCAL GetSeriesFilename(FileName : Text;FileNo : Integer) : Text
      +IF STRPOS(FileName,'\\') <> 0 THEN
      +    Directory := FileMgt.GetDirectoryName(FileName) + '\\';
      +EXIT(Directory + FileMgt.GetFileNameWithoutExtension(FileName) + FORMAT(FileNo) + '.' +   
      +    FileMgt.GetExtension(FileName));
      +

      Code 2: GetSeriesFilename

      The pattern depends on .NET library System.IO.Compression.ZipFile, which from NAV 2015 is implemented in Codeunit 419 (File management). It consists of three functions:

      • CreateZipArchiveObject: Which creates a System.IO.Compression.ZipArchive on a server side file
      • @@ -340,12 +338,12 @@ FileMgt.GetExtension(FileName));
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/multi-page-list/index.html b/navpatterns/1-patterns/multi-page-list/index.html index 12b4a477..4b46c705 100644 --- a/navpatterns/1-patterns/multi-page-list/index.html +++ b/navpatterns/1-patterns/multi-page-list/index.html @@ -5,16 +5,16 @@ Multi-Page List :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -250,11 +250,11 @@ ALGuidelines.Dev @@ -267,10 +267,9 @@ ALGuidelines.Dev

      Multi-Page List

      By Bogdana Botez at Microsoft Development Center Copenhagen

      -

      __

      -

      Abstract

      +

      Abstract

      This pattern describes how to open a related document (or card) page from a list page, for the case when there can be more than one pages associated to the rows of the list page.

      -

      Description

      +

      Description

      The example below illustrates the connection of a List Page with multiple Document Pages, while the second example links the List Page with Card Pages.

      The records contained in a list page have an associated page, which is specified in the CardPageID property of the list page. The NAV infrastructure assures the following scenarios are in place, without the need to add any explicit C/AL code:

      @@ -279,61 +278,66 @@ ALGuidelines.Dev
    • The “Edit” action is available on the ribbon as well as in the right-click context menu of the list page rows. Invoking this action, opens the related card page.
    • However, there are situations when rows of the list page can correspond to different pages each (either cards or documents). For example, consider a list page containing 3 rows, requiring the following behaviour:

      -

      Row1

      -

      Opens page ID 1

      -

      Row2

      -

      Opens page ID 2

      -

      Row3

      -

      Opens page ID 3

      + + + + + + + + + + + + + + + + + + + + + +
      Row1Opens page ID 1
      Row2Opens page ID 2
      Row3Opens page ID 3

      This situation is not handled automatically by NAV. There is no possibility to specify more than one CardPageID in the list page properties. Furthermore, there is no possibility to specify one or more document page IDs on the list page. Therefore, those cases need to be handled explicitly by the C/AL developer.

      -

      Usage

      +

      Usage

      The solution used in NAV implementations is at the list page level, as following:

        -
      1. The property CardPageID of the list page remains undefined.
      2. -
      3. An action named “Show Document” or “Card” is created on the Navigate tab, with the properties:
      4. -
      +
    • +

      The property CardPageID of the list page remains undefined.

      +
    • +
    • +

      An action named “Show Document” or “Card” is created on the Navigate tab, with the properties:

      • Image = EditLines
      • Promoted = Yes
      • ShortCutKey = Shift+F7
      -
        -
      1. The OnAction trigger for the Card action, contains explicit logic to run the targeted card page. It can, for example, be a CASE statement, which invokes PAGE.RUN(…) based on an enumeration field of the current row.
      2. + +
      3. +

        The OnAction trigger for the Card action, contains explicit logic to run the targeted card page. It can, for example, be a CASE statement, which invokes PAGE.RUN(…) based on an enumeration field of the current row.

        +
      - +

      For example, the NAV page Sales List (page ID 45), which displays the Sales Header Table (table ID 36), chooses which card to open, based on the Document Type field. This is an option field, which can have the following values: Quote, Order, Invoice, Credit Memo, Blanket Order, Return Order. For each document type, the related card page must be opened.

      For this purpose, a new action (“Card”) is added to the Sales List page. The OnAction trigger of this new action contains the page selection logic:

      -
      CASE "Document Type" OF
      -
      -"Document Type"::Quote:
      -
      -PAGE.RUN(PAGE::"Sales Quote",Rec);
      -
      -"Document Type"::Order:
      -
      -PAGE.RUN(PAGE::"Sales Order",Rec);
      -
      -"Document Type"::Invoice:
      -
      -PAGE.RUN(PAGE::"Sales Invoice",Rec);
      -
      -"Document Type"::"Return Order":
      -
      -PAGE.RUN(PAGE::"Sales Return Order",Rec);
      -
      -"Document Type"::"Credit Memo":
      -
      -PAGE.RUN(PAGE::"Sales Credit Memo",Rec);
      -
      -"Document Type"::"Blanket Order":[  
      -][anchor2]
      -
      -PAGE.RUN(PAGE::"Blanket Sales Order",Rec);
      -
      -END;
      -
      - +
      CASE "Document Type" OF
      +    "Document Type"::Quote:
      +        PAGE.RUN(PAGE::"Sales Quote",Rec);
      +    "Document Type"::Order:
      +        PAGE.RUN(PAGE::"Sales Order",Rec);
      +    "Document Type"::Invoice:
      +        PAGE.RUN(PAGE::"Sales Invoice",Rec);
      +    "Document Type"::"Return Order":
      +        PAGE.RUN(PAGE::"Sales Return Order",Rec);
      +    "Document Type"::"Credit Memo":
      +        PAGE.RUN(PAGE::"Sales Credit Memo",Rec);
      +    "Document Type"::"Blanket Order":
      +        PAGE.RUN(PAGE::"Blanket Sales Order",Rec);
      +END;
      +

      Some of the NAV implementations of this pattern can be found in the following pages:

      1. Sales List (45) and Sales List Archive (5159)
      2. @@ -354,12 +358,12 @@ END;
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/multilanguage-application-data/index.html b/navpatterns/1-patterns/multilanguage-application-data/index.html index 15fae414..33465285 100644 --- a/navpatterns/1-patterns/multilanguage-application-data/index.html +++ b/navpatterns/1-patterns/multilanguage-application-data/index.html @@ -5,16 +5,16 @@ Multilanguage Application Data :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -288,15 +288,29 @@ ALGuidelines.Dev

      Usage

      Create the translation table

      Named “<Entity> Translation” table, where <Entity> is replaced with the name of the actual object being translated. For the Item example above, this table will be named “Item Translation”.

      -

      The table definition contains at a minimum: -Field -Description -Entity ID field -For example, Item No. -Language Code -Identifies the language of this translation string (for example, “DAN” (Danish), “BGR”(Bulgarian). This is one of the language codes defined in the Languages table. -Translation -The translated string.

      +

      The table definition contains at a minimum:

      + + + + + + + + + + + + + + + + + + + + + +
      FieldDescription
      Entity ID fieldFor example, Item No.
      Language CodeIdentifies the language of this translation string (for example, “DAN” (Danish), “BGR”(Bulgarian). This is one of the language codes defined in the Languages table.
      TranslationThe translated string.

      The table above has a key composed of the first two fields.

      Create the user interface for entering new translations of <Entity>

        @@ -312,18 +326,33 @@ The translated string.

      • Unit of Measure Translation
      • -

        The Extended Text pattern is a more powerful version of the Multilanguage application data pattern presented in this section. The main differences are: Pattern -Multilanguage Application Data -Extended Text -Supports translation of application data -Yes -Yes -Format -Single- or multi-line -Single-line -Applies to document type -Can choose which document types are affected. -All document types are affected.

        +

        The Extended Text pattern is a more powerful version of the Multilanguage application data pattern presented in this section. The main differences are:

        + + + + + + + + + + + + + + + + + + + + + + + + + +
        PatternMultilanguage Application DataExtended Text
        Supports translation of application dataYesYes
        FormatSingle- or multi-lineSingle-line
        Applies to document typeCan choose which document types are affected.All document types are affected.
        @@ -336,12 +365,12 @@ All document types are affected.

        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/net-exception-handling-in-cal/index.html b/navpatterns/1-patterns/net-exception-handling-in-cal/index.html index db6732b8..2df23612 100644 --- a/navpatterns/1-patterns/net-exception-handling-in-cal/index.html +++ b/navpatterns/1-patterns/net-exception-handling-in-cal/index.html @@ -6,16 +6,16 @@ NET Exception Handling in CAL :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
    - - - + + +
      @@ -302,111 +302,131 @@ ALGuidelines.Dev

      For instance, if a failure to establish a connection to the web service due to lack of Internet access, a WebException is thrown with the relevant error details. COD1290 Web Service Mgt. codeunit wraps up the required functionality to interact with a web service in C/AL, handling all the required plumping work to send web requests, receive web responses, and extract valid and error details from the web response.

      Code Sample

      The “External Data Handling Codeunit” is a consumer of COD1290 Web Service Mgt. codeunit. If a runtime exception occurs, it gets handled as follows:

      -
      LOCAL PROCEDURE SendDataToConversionService@1(VAR PaymentFileTempBlob@1003 : Record 99008535;BodyTempBlob@1004 : Record 99008535;PostingExch@1007 : Record 1220);
      -VAR
      -BankDataConvServiceSetup@1000 : Record 1260;
      -WebServiceRequestMgt@1001 : Codeunit 1290;
      -BodyInStream@1005 : InStream;
      -ResponseInStream@1002 : InStream;
      -BEGIN
      -IF NOT BodyTempBlob.Blob.HASVALUE THEN
      -ERROR(NoRequestBodyErr);
      -PrepareSOAPRequestBody(BodyTempBlob);
      -COMMIT;
      -BankDataConvServiceSetup.GET;
      -BodyTempBlob.Blob.CREATEINSTREAM(BodyInStream);
      -WebServiceRequestMgt.SetGlobals(BodyInStream,
      -BankDataConvServiceSetup."Service URL",BankDataConvServiceSetup."User Name",BankDataConvServiceSetup.GetPassword);
      -IF NOT WebServiceRequestMgt.RUN THEN
      -WebServiceRequestMgt.ProcessFaultResponse;
      -WebServiceRequestMgt.GetResponseContent(ResponseInStream);
      -CheckIfErrorsOccurred(ResponseInStream,PostingExch);
      -ReadContentFromResponse(PaymentFileTempBlob,ResponseInStream);
      -END;
      +
      LOCAL PROCEDURE SendDataToConversionService@1(VAR PaymentFileTempBlob@1003 : Record 99008535;BodyTempBlob@1004 : Record 99008535;PostingExch@1007 : Record 1220);
      +VAR
      +    BankDataConvServiceSetup@1000 : Record 1260;
      +    WebServiceRequestMgt@1001 : Codeunit 1290;
      +    BodyInStream@1005 : InStream;
      +    ResponseInStream@1002 : InStream;
      +BEGIN
      +    IF NOT BodyTempBlob.Blob.HASVALUE THEN
      +        ERROR(NoRequestBodyErr);
       
      -PROCEDURE ProcessFaultResponse@15();
      -VAR
      -XMLDOMMgt@1006 : Codeunit 6224;
      -DotNetExceptionHandler@1000 : Codeunit 1291;
      -WebException@1005 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.WebException";
      -WebExceptionStatus@1004 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.WebExceptionStatus";
      -XmlDoc@1003 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlDocument";
      -HttpWebResponseError@1007 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpWebResponse";
      -HttpStatusCode@1008 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpStatusCode";
      -ResponseInputStream@1002 : InStream;
      -BEGIN
      -DotNetExceptionHandler.Collect;
      -IF NOT DotNetExceptionHandler.CastToType(WebException,GETDOTNETTYPE(WebException)) THEN
      -DotNetExceptionHandler.Rethrow;
      -IF NOT WebException.Status.Equals(WebExceptionStatus.ProtocolError) THEN
      -ERROR(WebException.Message);
      -ResponseInputStream := WebException.Response.GetResponseStream;
      -DebugLogStreamToTempFile(ResponseInputStream,'WebExceptionResponse',TempDebugLogTempBlob);
      -HttpWebResponseError := WebException.Response;
      -IF NOT (HttpWebResponseError.StatusCode.Equals(HttpStatusCode.Found) OR
      -HttpWebResponseError.StatusCode.Equals(HttpStatusCode.InternalServerError))
      -THEN
      -ERROR(WebException.Message);
      -XmlDoc := XmlDoc.XmlDocument;
      -XmlDoc.Load(ResponseInputStream);
      -ERROR(XMLDOMMgt.FindNodeTextWithNamespace(XmlDoc.DocumentElement,FaultStringXmlPathTxt,'soap',SoapNamespaceTxt));
      -END;
      +    PrepareSOAPRequestBody(BodyTempBlob);
       
      -OBJECT Codeunit 1291 DotNet Exception Handler
      +    COMMIT;
      +
      +    BankDataConvServiceSetup.GET;
      +    BodyTempBlob.Blob.CREATEINSTREAM(BodyInStream);
      +    WebServiceRequestMgt.SetGlobals(BodyInStream,
      +        BankDataConvServiceSetup."Service URL",BankDataConvServiceSetup."User Name",BankDataConvServiceSetup.GetPassword);
      +
      +    IF NOT WebServiceRequestMgt.RUN THEN
      +        WebServiceRequestMgt.ProcessFaultResponse;
      +
      +    WebServiceRequestMgt.GetResponseContent(ResponseInStream);
      +
      +    CheckIfErrorsOccurred(ResponseInStream,PostingExch);
      +
      +    ReadContentFromResponse(PaymentFileTempBlob,ResponseInStream);
      +END;
      +
      PROCEDURE ProcessFaultResponse@15();
      +VAR
      +    XMLDOMMgt@1006 : Codeunit 6224;
      +    DotNetExceptionHandler@1000 : Codeunit 1291;
      +    WebException@1005 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.WebException";
      +    WebExceptionStatus@1004 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.WebExceptionStatus";
      +    XmlDoc@1003 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlDocument";
      +    HttpWebResponseError@1007 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpWebResponse";
      +    HttpStatusCode@1008 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpStatusCode";
      +    ResponseInputStream@1002 : InStream;
      +BEGIN
      +    DotNetExceptionHandler.Collect;
      +
      +    IF NOT DotNetExceptionHandler.CastToType(WebException,GETDOTNETTYPE(WebException)) THEN
      +        DotNetExceptionHandler.Rethrow;
      +
      +    IF NOT WebException.Status.Equals(WebExceptionStatus.ProtocolError) THEN
      +        ERROR(WebException.Message);
      +
      +    ResponseInputStream := WebException.Response.GetResponseStream;
      +    DebugLogStreamToTempFile(ResponseInputStream,'WebExceptionResponse',TempDebugLogTempBlob);
      +
      +    HttpWebResponseError := WebException.Response;
      +    IF NOT (HttpWebResponseError.StatusCode.Equals(HttpStatusCode.Found) OR
      +        HttpWebResponseError.StatusCode.Equals(HttpStatusCode.InternalServerError))
      +    THEN
      +        ERROR(WebException.Message);
      +
      +    XmlDoc := XmlDoc.XmlDocument;
      +    XmlDoc.Load(ResponseInputStream);
      +
      +    ERROR(XMLDOMMgt.FindNodeTextWithNamespace(XmlDoc.DocumentElement,FaultStringXmlPathTxt,'soap',SoapNamespaceTxt));
      +END;
      +
      OBJECT Codeunit 1291 DotNet Exception Handler
       {
      -OBJECT-PROPERTIES
      -{
      -Date=;
      -Time=;
      -Version List=;
      +    OBJECT-PROPERTIES
      +    {
      +        Date=;
      +        Time=;
      +        Version List=;
      +    }
      +    PROPERTIES
      +    {
      +        OnRun=BEGIN
      +              END;
      +
      +    }
      +    CODE
      +    {
      +        VAR
      +            OuterException@1000 : DotNet "'mscorlib'.System.Exception";
      +    
      +        PROCEDURE Catch@3(VAR Exception@1002 : DotNet "'mscorlib'.System.FormatException";Type@1007 : DotNet "'mscorlib'.System.Type");
      +            BEGIN
      +                Collect;
      +                IF NOT CastToType(Exception,Type) THEN
      +                    Rethrow;
      +            END;
      +
      +        PROCEDURE Collect@1();
      +        BEGIN
      +            OuterException := GETLASTERROROBJECT;
      +        END;
      +
      +        PROCEDURE TryCastToType@5(Type@1000 : DotNet "'mscorlib'.System.Type") : Boolean;
      +        VAR
      +            Exception@1001 : DotNet "'mscorlib'.System.FormatException";
      +        BEGIN
      +            EXIT(CastToType(Exception,Type));
      +        END;
      +
      +        PROCEDURE CastToType@2(VAR Exception@1002 : DotNet "'mscorlib'.System.FormatException";Type@1007 : DotNet "'mscorlib'.System.Type") : Boolean;
      +        BEGIN
      +            Exception := OuterException;
      +
      +            REPEAT
      +                IF Type.Equals(Exception.GetType()) THEN
      +                    EXIT(TRUE);
      +                Exception := Exception.InnerException;
      +            UNTIL ISNULL(Exception);
      +
      +            EXIT(FALSE);
      +        END;
      +
      +        PROCEDURE Rethrow@4();
      +        BEGIN
      +            IF NOT ISNULL(OuterException.InnerException) THEN
      +                ERROR(OuterException.InnerException.Message);
      +
      +            ERROR(OuterException.Message);
      +        END;
      +        
      +        BEGIN
      +        END.
      +    }
       }
      -PROPERTIES
      -{
      -OnRun=BEGIN
      -END;
      -}
      -CODE
      -{
      -VAR
      -OuterException@1000 : DotNet "'mscorlib'.System.Exception";
      -PROCEDURE Catch@3(VAR Exception@1002 : DotNet "'mscorlib'.System.FormatException";Type@1007 : DotNet "'mscorlib'.System.Type");
      -BEGIN
      -Collect;
      -IF NOT CastToType(Exception,Type) THEN
      -Rethrow;
      -END;
      -PROCEDURE Collect@1();
      -BEGIN
      -OuterException := GETLASTERROROBJECT;
      -END;
      -PROCEDURE TryCastToType@5(Type@1000 : DotNet "'mscorlib'.System.Type") : Boolean;
      -VAR
      -Exception@1001 : DotNet "'mscorlib'.System.FormatException";
      -BEGIN
      -EXIT(CastToType(Exception,Type));
      -END;
      -PROCEDURE CastToType@2(VAR Exception@1002 : DotNet "'mscorlib'.System.FormatException";Type@1007 : DotNet "'mscorlib'.System.Type") : Boolean;
      -BEGIN
      -Exception := OuterException;
      -REPEAT
      -IF Type.Equals(Exception.GetType()) THEN
      -EXIT(TRUE);
      -Exception := Exception.InnerException;
      -UNTIL ISNULL(Exception);
      -EXIT(FALSE);
      -END;
      -PROCEDURE Rethrow@4();
      -BEGIN
      -IF NOT ISNULL(OuterException.InnerException) THEN
      -ERROR(OuterException.InnerException.Message);
      -ERROR(OuterException.Message);
      -END;
      -BEGIN
      -END.
      -}
      -}
      -
      - +

    The DotNet Exception Handler codeunit has been used for the Web service integration required for:

    1. Payment Export from the Payment Journal for creating bank-specific payment files.
    2. @@ -429,12 +449,12 @@ END.
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/net-exception-handling-in-cal/tryfunction-net-exception-handling-in-cal/index.html b/navpatterns/1-patterns/net-exception-handling-in-cal/tryfunction-net-exception-handling-in-cal/index.html index 35fffd3a..fd4f8cd1 100644 --- a/navpatterns/1-patterns/net-exception-handling-in-cal/tryfunction-net-exception-handling-in-cal/index.html +++ b/navpatterns/1-patterns/net-exception-handling-in-cal/tryfunction-net-exception-handling-in-cal/index.html @@ -5,16 +5,16 @@ TryFunction NET Exception Handling in CAL :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -300,129 +300,153 @@ ALGuidelines.Dev

        For instance, if a failure to establish a connection to the web service due to lack of Internet access, a WebException is thrown with the relevant error details. COD1290 Web Service Mgt. codeunit wraps up the required functionality to interact with a web service in C/AL, handling all the required plumping work to send web requests, receive web responses, and extract valid and error details from the web response.

        Code Sample

        The “External Data Handling Codeunit” is a consumer of COD1290 Web Service Mgt. codeunit. If a runtime exception occurs, it gets handled as follows:

        -
        \[TryFunction\]
        -PROCEDURE SendRequestToWebService@17();
        -VAR
        -WebRequestHelper@1000 : Codeunit 1299;
        -HttpWebRequest@1007 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpWebRequest";
        -HttpStatusCode@1002 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpStatusCode";
        -ResponseHeaders@1001 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Collections.Specialized.NameValueCollection";
        -ResponseInStream@1006 : InStream;
        -BEGIN
        -CheckGlobals;
        -BuildWebRequest(GlobalURL,HttpWebRequest);
        -ResponseInStreamTempBlob.INIT;
        -ResponseInStreamTempBlob.Blob.CREATEINSTREAM(ResponseInStream);
        -CreateSoapRequest(HttpWebRequest.GetRequestStream,GlobalRequestBodyInStream,GlobalUsername,GlobalPassword);
        -WebRequestHelper.GetWebResponse(HttpWebRequest,HttpWebResponse,ResponseInStream,HttpStatusCode,ResponseHeaders,GlobalProgressDialogEnabled);
        -ExtractContentFromResponse(ResponseInStream,ResponseBodyTempBlob);
        -END;
        +
        [TryFunction]
        +PROCEDURE SendRequestToWebService@17();
        +VAR
        +    WebRequestHelper@1000 : Codeunit 1299;
        +    HttpWebRequest@1007 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpWebRequest";
        +    HttpStatusCode@1002 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpStatusCode";
        +    ResponseHeaders@1001 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Collections.Specialized.NameValueCollection";
        +    ResponseInStream@1006 : InStream;
        +BEGIN
        +    CheckGlobals;
         
        -LOCAL PROCEDURE SendDataToConversionService@1(VAR PaymentFileTempBlob@1003 : Record 99008535;BodyTempBlob@1004 : Record 99008535;PostingExch@1007 : Record 1220);
        -VAR
        -BankDataConvServiceSetup@1000 : Record 1260;
        -WebServiceRequestMgt@1001 : Codeunit 1290;
        -BodyInStream@1005 : InStream;
        -ResponseInStream@1002 : InStream;
        -BEGIN
        -IF NOT BodyTempBlob.Blob.HASVALUE THEN
        -ERROR(NoRequestBodyErr);
        -PrepareSOAPRequestBody(BodyTempBlob);
        -COMMIT;
        -BankDataConvServiceSetup.GET;
        -BodyTempBlob.Blob.CREATEINSTREAM(BodyInStream);
        -WebServiceRequestMgt.SetGlobals(BodyInStream,
        -BankDataConvServiceSetup."Service URL",BankDataConvServiceSetup."User Name",BankDataConvServiceSetup.GetPassword);
        -IF NOT WebServiceRequestMgt.SendRequestToWebService THEN
        -WebServiceRequestMgt.ProcessFaultResponse;
        -WebServiceRequestMgt.GetResponseContent(ResponseInStream);
        -CheckIfErrorsOccurred(ResponseInStream,PostingExch);
        -ReadContentFromResponse(PaymentFileTempBlob,ResponseInStream);
        -END;
        +    BuildWebRequest(GlobalURL,HttpWebRequest);
         
        -PROCEDURE ProcessFaultResponse@15();
        -VAR
        -XMLDOMMgt@1006 : Codeunit 6224;
        -DotNetExceptionHandler@1000 : Codeunit 1291;
        -WebException@1005 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.WebException";
        -WebExceptionStatus@1004 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.WebExceptionStatus";
        -XmlDoc@1003 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlDocument";
        -HttpWebResponseError@1007 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpWebResponse";
        -HttpStatusCode@1008 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpStatusCode";
        -ResponseInputStream@1002 : InStream;
        -BEGIN
        -DotNetExceptionHandler.Collect;
        -IF NOT DotNetExceptionHandler.CastToType(WebException,GETDOTNETTYPE(WebException)) THEN
        -DotNetExceptionHandler.Rethrow;
        -IF NOT WebException.Status.Equals(WebExceptionStatus.ProtocolError) THEN
        -ERROR(WebException.Message);
        -ResponseInputStream := WebException.Response.GetResponseStream;
        -DebugLogStreamToTempFile(ResponseInputStream,'WebExceptionResponse',TempDebugLogTempBlob);
        -HttpWebResponseError := WebException.Response;
        -IF NOT (HttpWebResponseError.StatusCode.Equals(HttpStatusCode.Found) OR
        -HttpWebResponseError.StatusCode.Equals(HttpStatusCode.InternalServerError))
        -THEN
        -ERROR(WebException.Message);
        -XmlDoc := XmlDoc.XmlDocument;
        -XmlDoc.Load(ResponseInputStream);
        -ERROR(XMLDOMMgt.FindNodeTextWithNamespace(XmlDoc.DocumentElement,FaultStringXmlPathTxt,'soap',SoapNamespaceTxt));
        -END;
        +    ResponseInStreamTempBlob.INIT;
        +    ResponseInStreamTempBlob.Blob.CREATEINSTREAM(ResponseInStream);
         
        -OBJECT Codeunit 1291 DotNet Exception Handler
        +    CreateSoapRequest(HttpWebRequest.GetRequestStream,GlobalRequestBodyInStream,GlobalUsername,GlobalPassword);
        +
        +    WebRequestHelper.GetWebResponse(HttpWebRequest,HttpWebResponse,ResponseInStream,
        +        HttpStatusCode,ResponseHeaders,GlobalProgressDialogEnabled);
        +
        +    ExtractContentFromResponse(ResponseInStream,ResponseBodyTempBlob);
        +END;
        +
        LOCAL PROCEDURE SendDataToConversionService@1(VAR PaymentFileTempBlob@1003 : Record 99008535;BodyTempBlob@1004 : Record 99008535;PostingExch@1007 : Record 1220);
        +VAR
        +    BankDataConvServiceSetup@1000 : Record 1260;
        +    WebServiceRequestMgt@1001 : Codeunit 1290;
        +    BodyInStream@1005 : InStream;
        +    ResponseInStream@1002 : InStream;
        +BEGIN
        +    IF NOT BodyTempBlob.Blob.HASVALUE THEN
        +        ERROR(NoRequestBodyErr);
        +
        +    PrepareSOAPRequestBody(BodyTempBlob);
        +
        +    COMMIT;
        +
        +    BankDataConvServiceSetup.GET;
        +    BodyTempBlob.Blob.CREATEINSTREAM(BodyInStream);
        +    WebServiceRequestMgt.SetGlobals(BodyInStream,BankDataConvServiceSetup."Service URL",
        +        BankDataConvServiceSetup."User Name",BankDataConvServiceSetup.GetPassword);
        +
        +    IF NOT WebServiceRequestMgt.SendRequestToWebService THEN
        +        WebServiceRequestMgt.ProcessFaultResponse;
        +
        +    WebServiceRequestMgt.GetResponseContent(ResponseInStream);
        +
        +    CheckIfErrorsOccurred(ResponseInStream,PostingExch);
        +
        +    ReadContentFromResponse(PaymentFileTempBlob,ResponseInStream);
        +END;
        +
        PROCEDURE ProcessFaultResponse@15();
        +VAR
        +    XMLDOMMgt@1006 : Codeunit 6224;
        +    DotNetExceptionHandler@1000 : Codeunit 1291;
        +    WebException@1005 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.WebException";
        +    WebExceptionStatus@1004 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.WebExceptionStatus";
        +    XmlDoc@1003 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlDocument";
        +    HttpWebResponseError@1007 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpWebResponse";
        +    HttpStatusCode@1008 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpStatusCode";
        +    ResponseInputStream@1002 : InStream;
        +BEGIN
        +    DotNetExceptionHandler.Collect;
        +
        +    IF NOT DotNetExceptionHandler.CastToType(WebException,GETDOTNETTYPE(WebException)) THEN
        +        DotNetExceptionHandler.Rethrow;
        +
        +    IF NOT WebException.Status.Equals(WebExceptionStatus.ProtocolError) THEN
        +        ERROR(WebException.Message);
        +
        +    ResponseInputStream := WebException.Response.GetResponseStream;
        +    DebugLogStreamToTempFile(ResponseInputStream,'WebExceptionResponse',TempDebugLogTempBlob);
        +
        +    HttpWebResponseError := WebException.Response;
        +    IF NOT (HttpWebResponseError.StatusCode.Equals(HttpStatusCode.Found) OR
        +        HttpWebResponseError.StatusCode.Equals(HttpStatusCode.InternalServerError))
        +    THEN
        +        ERROR(WebException.Message);
        +
        +    XmlDoc := XmlDoc.XmlDocument;
        +    XmlDoc.Load(ResponseInputStream);
        +
        +    ERROR(XMLDOMMgt.FindNodeTextWithNamespace(XmlDoc.DocumentElement,FaultStringXmlPathTxt,'soap',SoapNamespaceTxt));
        +END;
        +
        OBJECT Codeunit 1291 DotNet Exception Handler
         {
        -OBJECT-PROPERTIES
        -{
        -Date=;
        -Time=;
        -Version List=;
        +    OBJECT-PROPERTIES
        +    {
        +        Date=;
        +        Time=;
        +        Version List=;
        +    }
        +    PROPERTIES
        +    {
        +        OnRun=BEGIN
        +              END;
        +    }
        +    CODE
        +    {
        +        VAR
        +            OuterException@1000 : DotNet "'mscorlib'.System.Exception";
        +        
        +        PROCEDURE Catch@3(VAR Exception@1002 : DotNet "'mscorlib'.System.FormatException";Type@1007 : DotNet "'mscorlib'.System.Type");
        +        BEGIN
        +            Collect;
        +            IF NOT CastToType(Exception,Type) THEN
        +                Rethrow;
        +        END;
        +
        +        PROCEDURE Collect@1();
        +        BEGIN
        +            OuterException := GETLASTERROROBJECT;
        +        END;
        +
        +        PROCEDURE TryCastToType@5(Type@1000 : DotNet "'mscorlib'.System.Type") : Boolean;
        +        VAR
        +            Exception@1001 : DotNet "'mscorlib'.System.FormatException";
        +        BEGIN
        +            EXIT(CastToType(Exception,Type));
        +        END;
        +
        +        PROCEDURE CastToType@2(VAR Exception@1002 : DotNet "'mscorlib'.System.FormatException";Type@1007 : DotNet "'mscorlib'.System.Type") : Boolean;
        +        BEGIN
        +            Exception := OuterException;
        +
        +            REPEAT
        +                IF Type.Equals(Exception.GetType()) THEN
        +                    EXIT(TRUE);
        +                Exception := Exception.InnerException;
        +            UNTIL ISNULL(Exception);
        +
        +            EXIT(FALSE);
        +        END;
        +
        +        PROCEDURE Rethrow@4();
        +        BEGIN
        +            IF NOT ISNULL(OuterException.InnerException) THEN
        +                ERROR(OuterException.InnerException.Message);
        +
        +            ERROR(OuterException.Message);
        +        END;
        +
        +        BEGIN
        +        END.
        +    }
         }
        -PROPERTIES
        -{
        -OnRun=BEGIN
        -END;
        -}
        -CODE
        -{
        -VAR
        -OuterException@1000 : DotNet "'mscorlib'.System.Exception";
        -PROCEDURE Catch@3(VAR Exception@1002 : DotNet "'mscorlib'.System.FormatException";Type@1007 : DotNet "'mscorlib'.System.Type");
        -BEGIN
        -Collect;
        -IF NOT CastToType(Exception,Type) THEN
        -Rethrow;
        -END;
        -PROCEDURE Collect@1();
        -BEGIN
        -OuterException := GETLASTERROROBJECT;
        -END;
        -PROCEDURE TryCastToType@5(Type@1000 : DotNet "'mscorlib'.System.Type") : Boolean;
        -VAR
        -Exception@1001 : DotNet "'mscorlib'.System.FormatException";
        -BEGIN
        -EXIT(CastToType(Exception,Type));
        -END;
        -PROCEDURE CastToType@2(VAR Exception@1002 : DotNet "'mscorlib'.System.FormatException";Type@1007 : DotNet "'mscorlib'.System.Type") : Boolean;
        -BEGIN
        -Exception := OuterException;
        -REPEAT
        -IF Type.Equals(Exception.GetType()) THEN
        -EXIT(TRUE);
        -Exception := Exception.InnerException;
        -UNTIL ISNULL(Exception);
        -EXIT(FALSE);
        -END;
        -PROCEDURE Rethrow@4();
        -BEGIN
        -IF NOT ISNULL(OuterException.InnerException) THEN
        -ERROR(OuterException.InnerException.Message);
        -ERROR(OuterException.Message);
        -END;
        -BEGIN
        -END.
        -}
        -}
        -
        - +

      The CO1291 DotNet Exception Handler codeunit has been used for the Web service integration required for:

      1. Payment Export from the Payment Journal for creating bank-specific payment files.
      2. @@ -441,12 +465,12 @@ END.
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/no-series/index.html b/navpatterns/1-patterns/no-series/index.html index 0b767f11..0121fc1d 100644 --- a/navpatterns/1-patterns/no-series/index.html +++ b/navpatterns/1-patterns/no-series/index.html @@ -5,16 +5,16 @@ No Series :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
          @@ -261,7 +261,7 @@ ALGuidelines.Dev
        • NAV Usages
          • Sales and Receivables
          • -
          • +
          • Field “No.”
          • Field “No. Series”
          • AssistEdit
          • OnInsert
          • @@ -292,68 +292,133 @@ ALGuidelines.Dev

            From the implementation point of view, a number series is a record in the table 308 - “No. Series”.

            The most relevant fields are:

            -

            Code

            -

            Code 10

            -

            Used to identify the number series in further places where it will be used.

            -

            Default Nos.

            -

            Boolean

            -

            The boolean Default Nos. decides whether this number series can use automatic numbering. If false, then the user is expected to manually look up the No. field and select it from the number series.

            -

            Manual Nos.

            -

            Boolean

            -

            If the Manual Nos. is Yes, then the used will be allowed to manually type the value of the next number (“No.") field.

            -

            Date Order

            -

            Boolean

            -

            Date Order is used to decide whether or not the numbers from the range are assigned chronologically.

            + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            CodeCode 10Used to identify the number series in further places where it will be used.
            Default Nos.BooleanThe boolean Default Nos. decides whether this number series can use automatic numbering. If false, then the user is expected to manually look up the No. field and select it from the number series.
            Manual Nos.BooleanIf the Manual Nos. is Yes, then the used will be allowed to manually type the value of the next number (“No.") field.
            Date OrderBooleanDate Order is used to decide whether or not the numbers from the range are assigned chronologically.

            Number Series sequence

            The sequence definition is found in table 309 - No. Series Line. Contains the explicit definition of the series, for example a series called BANK, could start with BANK001 and increase incrementally by one, in the sequence BANK001, BANK002, BANK003, …. Until the last number BANK999. When hitting the number BANK990, the user will receive a warning that the series is about to be exhausted and it must either be increased, or a new series should be assigned.

            The most relevant fields are:

            -

            Series Code

            -

            Code 10

            -

            Links it to the number series defined in table 308.

            -

            Starting No.

            -

            Integer

            -

            This is the first number in the sequence, for a book indexing application it can be BOOK0001.

            -

            Ending No.

            -

            Integer

            -

            The last number in the sequence, for example BOOK5000.

            -

            Warning No.

            -

            Integer

            -

            When this number is reached, the user will see a warning message stating that the number series is running out of assignable numbers.

            -

            Increment-by No.

            -

            Integer

            -

            The value for incrementing the numeric part of the series.

            -

            Last No. Used

            -

            Code 20

            -

            The last number from the sequence that was assigned.

            -

            Last Date Used

            -

            Date

            -

            Stating when the last number was assigned.

            + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            Series CodeCode 10Links it to the number series defined in table 308.
            Starting No.IntegerThis is the first number in the sequence, for a book indexing application it can be BOOK0001.
            Ending No.IntegerThe last number in the sequence, for example BOOK5000.
            Warning No.IntegerWhen this number is reached, the user will see a warning message stating that the number series is running out of assignable numbers.
            Increment-by No.IntegerThe value for incrementing the numeric part of the series.
            Last No. UsedCode 20The last number from the sequence that was assigned.
            Last Date UsedDateStating when the last number was assigned.

            Add the default number series to the setup

            Various series of numbers can be defined as seen above. For example, a library can have a number series for indexing rental of each of the following: books, movies, music, video. The books series can be split by domain, for example a series BOOKSCIFI can have BOOK0001…BOOK1500, then BOOKTECH can use the sequence BOOK1501…BOOK4000 and so on.

            The default number series for a certain application area is typically stored in the domain setup table. For example, NAV Demo Database stores the default Item number series in the Inventory Setup table 313. This means that you will need to add the Number Series fields in your setup table and ensure that before the customer starts using the solution, the number series is created and added to the setup defaults.

            How to use the Number Series

            The table which will host the number from the number series, needs the following fields:

            -

            No.

            -

            Code 20

            -

            Contains the auto-generated sequential number.

            -

            No. Series

            -

            Code 10

            -

            The number series definition, which decides what the next No. will be.

            + + + + + + + + + + + + + + + + + + + + +
            No.Code 20Contains the auto-generated sequential number.
            No. SeriesCode 10The number series definition, which decides what the next No. will be.

            And the code to make the number series alive:

            OnInsert

            -

            OnInsert()

            -

            IF “No.” = '' THEN

            -

            NoSeriesMgt.InitSeries(DefaultNoSeriesCode,OldNoSeriesCode,NewDate,NewNo,NewNoSeriesCode);

            -

            Field “No."

            -

            No. - OnValidate()

            -

            IF “No.” <> xRec.“No.” THEN BEGIN // Validate that “No.” corresponds to the current No. Series rules NoSeriesMgt.TestManual(DefaultNoSeriesCode); “No. Series” := ‘'; END;

            -

            AssistEdit

            -

            AssistEdit() : Boolean

            -

            IF “No.” = '’ THEN

            -

            NoSeriesMgt.InitSeries(DefaultNoSeriesCode,OldNoSeriesCode,NewDate,NewNo,NewNoSeriesCode);

            -

            Where:

            +
            OnInsert()
            +
            +IF "No." = '' THEN 
            +    NoSeriesMgt.InitSeries(DefaultNoSeriesCode,OldNoSeriesCode,NewDate,NewNo,NewNoSeriesCode);
            +

            Field “No."

            +
            No. - OnValidate()
            +
            +IF "No." <> xRec."No." THEN BEGIN // Validate that "No." corresponds to the current No. Series rules 
            +    NoSeriesMgt.TestManual(DefaultNoSeriesCode);
            +    "No. Series" := '';
            +END;
            +

            AssistEdit

            +
            AssistEdit() : Boolean
            +
            +IF "No." = '' THEN 
            +    NoSeriesMgt.InitSeries(DefaultNoSeriesCode,OldNoSeriesCode,NewDate,NewNo,NewNoSeriesCode);
            +

            Where:

            • DefaultNoSeriesCode is typically found in the application domain setup table.
            • OldNoSeriesCode is typically the previous value of the “No. Series” code, which is found in xRec.“No. Series”
            • @@ -365,51 +430,61 @@ ALGuidelines.Dev

              Sales and Receivables

              The setup table 311 (Sales & Receivables Setup) contains the default number series to be used in the whole application domain. In the demo data, some of the series defined here are: Customer Nos., Quote Nos., Order Nos., Invoice Nos., Posted Invoice Nos., Credit Memo Nos., Posted Credit Memo Nos., etc. Those number series are dimensioned for the needs of a typical small to medium sized company, however, various business have different patterns (for example, posting an unusual high number of invoices). In this case, the number series should be adjusted accordingly to make space for the customized company needs.

              The default number series defined in the setup is then used in the individual tables. For example, table 18 - Customer, has

              -

              -

              Field “No.”

              -

              { 1 ; ;No. ;Code20 ;AltSearchField=Search Name;

              -

              OnValidate=

              -

              BEGIN

              -

              IF “No.” <> xRec.“No.” THEN BEGIN

              -

              SalesSetup.GET;

              -

              NoSeriesMgt.TestManual(SalesSetup.“Customer Nos.");

              -

              “No. Series” := ‘';

              -

              END;

              -

              IF “Invoice Disc. Code” = '’ THEN

              -

              “Invoice Disc. Code” := “No.";

              -

              END;

              -

              }

              -

              Field “No. Series”

              -

              { 107 ; ;No. Series ;Code10 ;TableRelation=“No. Series”; Editable=No }

              -

              AssistEdit

              -

              PROCEDURE AssistEdit@2(OldCust@1000 : Record 18) : Boolean;

              -

              VAR Cust@1001 : Record 18;

              -

              BEGIN

              -

              WITH Cust DO BEGIN

              -

              Cust := Rec;

              -

              SalesSetup.GET;

              -

              SalesSetup.TESTFIELD(“Customer Nos.");

              -

              IF NoSeriesMgt.SelectSeries(SalesSetup.“Customer Nos.",OldCust.“No. Series”,“No. Series”) THEN BEGIN

              -

              NoSeriesMgt.SetSeries(“No.");

              -

              Rec := Cust;

              -

              EXIT(TRUE);

              -

              END;

              -

              END;

              -

              END;

              -

              OnInsert

              -

              OnInsert=
              -BEGIN

              -

              IF “No.” = '' THEN BEGIN

              -

              SalesSetup.GET;

              -

              SalesSetup.TESTFIELD(“Customer Nos.");

              -

              NoSeriesMgt.InitSeries(SalesSetup.“Customer Nos.",xRec.“No. Series”,0D,“No.”,“No. Series”);

              -

              END;

              -

              -

              END

              -

              To run the AssistEdit procedure, include this code on the No. - OnAssistEdit() trigger of the Page:

              +

              Field “No.”

              +
              { 1 ; ;No. ;Code20 ;AltSearchField=Search Name;
              +
              +OnValidate=
              +
              +BEGIN
              +    IF "No." <> xRec."No." THEN BEGIN
              +        SalesSetup.GET;
              +
              +        NoSeriesMgt.TestManual(SalesSetup."Customer Nos.");
              +
              +        "No. Series" := '';
              +    END;
              +
              +    IF "Invoice Disc. Code" = '' THEN
              +        "Invoice Disc. Code" := "No.";
              +END;
              +} 
              +

              Field “No. Series”

              +
              { 107 ; ;No. Series ;Code10 ;TableRelation="No. Series"; Editable=No }
              +

              AssistEdit

              +
              PROCEDURE AssistEdit@2(OldCust@1000 : Record 18) : Boolean;
              +VAR
              +    Cust@1001 : Record 18;
              +BEGIN
              +    WITH Cust DO BEGIN
              +        Cust := Rec; 
              +        SalesSetup.GET; 
              +
              +        SalesSetup.TESTFIELD("Customer Nos."); 
              +
              +        IF NoSeriesMgt.SelectSeries(SalesSetup."Customer Nos.",OldCust."No. Series","No. Series") THEN BEGIN
              +            NoSeriesMgt.SetSeries("No."); 
              +            Rec := Cust; 
              +            EXIT(TRUE); 
              +        END; 
              +    END;
              +END;
              +

              OnInsert

              +
              OnInsert=  
              +    BEGIN 
              +        IF "No." = '' THEN BEGIN 
              +            SalesSetup.GET; 
              +
              +            SalesSetup.TESTFIELD("Customer Nos.");
              +
              +            NoSeriesMgt.InitSeries(SalesSetup."Customer Nos.",xRec."No. Series",0D,"No.","No. Series"); 
              +        END;
              +    ...
              +    END
              +

              To run the AssistEdit procedure, include this code on the No. - OnAssistEdit() trigger of the Page:

              No. - OnAssistEdit()

              -

              IF AssistEdit(xRec) THEN

              -

              CurrPage.UPDATE;

              +
              IF AssistEdit(xRec) THEN
              +    CurrPage.UPDATE;
              +
              @@ -425,12 +500,12 @@ BEGIN

              - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/notifications/in-context-notifications/index.html b/navpatterns/1-patterns/notifications/in-context-notifications/index.html index 58ab57e1..e28a8921 100644 --- a/navpatterns/1-patterns/notifications/in-context-notifications/index.html +++ b/navpatterns/1-patterns/notifications/in-context-notifications/index.html @@ -5,16 +5,16 @@ In-context Notifications :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
        - - - + + + @@ -306,7 +308,7 @@ ALGuidelines.Dev

        The ID is an optional parameter for the notification object that tracks the object in communications between the client and the server. Notifications have unique IDs that can be hard coded as GUIDs, as shown in Figure 5. A GUID can be generated using the CREATEGUID system function.

        COD 311: Item-Check Avail.

        GetItemAvailabilityNotificationId

        -

        EXIT(‘2712AD06-C48B-4C20-820E-347A60C9AD00’);****

        +

        EXIT(‘2712AD06-C48B-4C20-820E-347A60C9AD00’);

        Figure 5. Uniquely identifying a notification

        If the call to set ID is skipped, sending the notification creates a new notification with an ID that is generated at run-time instead of updating a notification that is already displayed (if present) with the ID.

        Including messages notifications

        @@ -385,7 +387,7 @@ ALGuidelines.Dev
      3. OnStateChanged event should be subscribed to if the developer needs to do something additional when changing the state of a notification, such as turn on another notification.

      The ability to turn notifications on or off is not required. If skipped, the notification is always shown when the condition that triggers it is met, and a user cannot turn it off.

      -

      NAV specific usages

      +

      For examples of how these objects are used in Dynamics NAV, look at the code for the following objects:

      1. Codeunit 1802 Data Migration Notifier
      2. @@ -394,7 +396,7 @@ ALGuidelines.Dev
      3. Codeunit 1854 Item Sales Forecast Notifier (in SalesAndInventoryForecast extension)
      4. Codeunit 1852 Item Sales Forecast Scheduler (in SalesAndInventoryForecast extension)
      -

      Best practices

      +

      Best practices

      The following list summarizes best practices for creating notifications:

      1. Do not set data on the notification that you will not use in the method invoked from the action button.
      2. @@ -414,12 +416,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/notifications/index.html b/navpatterns/1-patterns/notifications/index.html index d17d68c4..ea7c6587 100644 --- a/navpatterns/1-patterns/notifications/index.html +++ b/navpatterns/1-patterns/notifications/index.html @@ -6,16 +6,16 @@ Notifications :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev - - - + + +
          @@ -268,12 +268,12 @@ ALGuidelines.Dev
          - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/notifications/notification-lifecycle-management-pattern/index.html b/navpatterns/1-patterns/notifications/notification-lifecycle-management-pattern/index.html index e764ded8..b758f4a2 100644 --- a/navpatterns/1-patterns/notifications/notification-lifecycle-management-pattern/index.html +++ b/navpatterns/1-patterns/notifications/notification-lifecycle-management-pattern/index.html @@ -5,16 +5,16 @@ Notification Lifecycle Management Pattern :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
        - - - + + +
          @@ -263,48 +263,49 @@ ALGuidelines.Dev

          Description

          Notifications are easy to use in a wide range of cases. Instead of using notifications in a fire-and-forget way, we need to track them so that we can recall them if we need to.

          -

          If we can have only one notification on a given page, an easy and efficient solution is to use a predefined Notification ID, as suggested in the “Using In-context Notifications” pattern.

          +

          If we can have only one notification on a given page, an easy and efficient solution is to use a predefined Notification ID, as suggested in the “Using In-context Notifications” pattern.

          However, some cases can be more complicated. For example, when you are adding lines to a table, what if several lines raise individual notifications? Using the same notification ID for each notification will no longer work because the latest notification overwrites the previous ones. Only one notification for a given notification ID can exist, and only the notification message would be updated. This is illustrated in Figure 1.

          Figure 1: Notification that an item that is not in stock. The notification ID is a predefined GUID, 2712AD06-C48B-4C20-820E-347A60C9AD00, for example.

          Figure 2: You add a second item that is not in stock. the notification is fired with the same GUID, 2712AD06-C48B-4C20-820E-347A60C9AD00, for example. The previous notification is overwritten.

          Here is the code for this behavior:

          -
          LOCAL PROCEDURE CreateAndSendNotification@23(UnitOfMeasureCode@1010 : Code\[20\];InventoryQty@1009 : Decimal;GrossReq@1008 : Decimal;ReservedReq@1007 : Decimal;SchedRcpt@1006 : Decimal;ReservedRcpt@1005 : Decimal;CurrentQuantity@1004 : Decimal;CurrentReservedQty@1003 : Decimal;TotalQuantity@1002 : Decimal;EarliestAvailDate@1001 : Date) : Boolean; 
          -VAR 
          -ItemAvailabilityCheck@1011 : Page 1872; 
          -AvailabilityCheckNotification@1000 : Notification; 
          -BEGIN 
          -**AvailabilityCheckNotification.ID(GetItemAvailabilityNotificationId);**
          -AvailabilityCheckNotification.MESSAGE(STRSUBSTNO(NotificationMsg,ItemNo)); 
          -AvailabilityCheckNotification.SCOPE(NOTIFICATIONSCOPE::LocalScope); 
          -AvailabilityCheckNotification.ADDACTION(DetailsTxt,CODEUNIT::"Item-Check Avail.",'ShowNotificationDetails'); 
          -ItemAvailabilityCheck.PopulateDataOnNotification(AvailabilityCheckNotification,ItemNo,UnitOfMeasureCode,InventoryQty,GrossReq,ReservedReq,SchedRcpt,ReservedRcpt,CurrentQuantity,CurrentReservedQty,TotalQuantity,EarliestAvailDate); 
          -AvailabilityCheckNotification.SEND; 
          -EXIT(FALSE); 
          -END;  
          -LOCAL PROCEDURE **GetItemAvailabilityNotificationId**@27() : GUID; 
          -BEGIN 
          -**EXIT('2712AD06-C48B-4C20-820E-347A60C9AD00');**
          -END; 
          -
          -

          An easy fix would be to dynamically generate the notification ID. However, what if you fix the issue that triggered the notification?

          +
          LOCAL PROCEDURE CreateAndSendNotification@23(UnitOfMeasureCode@1010 : Code[20];InventoryQty@1009 : Decimal;GrossReq@1008 : Decimal;ReservedReq@1007 : Decimal;SchedRcpt@1006 : Decimal;ReservedRcpt@1005 : Decimal;CurrentQuantity@1004 : Decimal;CurrentReservedQty@1003 : Decimal;TotalQuantity@1002 : Decimal;EarliestAvailDate@1001 : Date) : Boolean; 
          +VAR 
          +  ItemAvailabilityCheck@1011 : Page 1872; 
          +  AvailabilityCheckNotification@1000 : Notification; 
          +BEGIN 
          +  AvailabilityCheckNotification.ID(GetItemAvailabilityNotificationId);
          +  AvailabilityCheckNotification.MESSAGE(STRSUBSTNO(NotificationMsg,ItemNo)); 
          +  AvailabilityCheckNotification.SCOPE(NOTIFICATIONSCOPE::LocalScope); 
          +  AvailabilityCheckNotification.ADDACTION(DetailsTxt,CODEUNIT::"Item-Check Avail.",'ShowNotificationDetails');
          +
          +  ItemAvailabilityCheck.PopulateDataOnNotification(AvailabilityCheckNotification,ItemNo,UnitOfMeasureCode,InventoryQty,GrossReq,ReservedReq,SchedRcpt,ReservedRcpt,CurrentQuantity,CurrentReservedQty,TotalQuantity,EarliestAvailDate); 
          +  AvailabilityCheckNotification.SEND; 
          +  EXIT(FALSE); 
          +END;
          +
          +LOCAL PROCEDURE **GetItemAvailabilityNotificationId**@27() : GUID; 
          +BEGIN 
          +  EXIT('2712AD06-C48B-4C20-820E-347A60C9AD00');
          +END; 
          +

          An easy fix would be to dynamically generate the notification ID. However, what if you fix the issue that triggered the notification?

          Here is the code for this possible fix:

          -
          LOCAL PROCEDURE CreateAndSendNotification@23(UnitOfMeasureCode@1010 : Code\[20\];InventoryQty@1009 : Decimal;GrossReq@1008 : Decimal;ReservedReq@1007 : Decimal;SchedRcpt@1006 : Decimal;ReservedRcpt@1005 : Decimal;CurrentQuantity@1004 : Decimal;CurrentReservedQty@1003 : Decimal;TotalQuantity@1002 : Decimal;EarliestAvailDate@1001 : Date) : Boolean; 
          -VAR 
          -ItemAvailabilityCheck@1011 : Page 1872; 
          -AvailabilityCheckNotification@1000 : Notification; 
          -BEGIN 
          -**AvailabilityCheckNotification.ID(CREATEGUID);**
          -AvailabilityCheckNotification.MESSAGE(STRSUBSTNO(NotificationMsg,ItemNo)); 
          -AvailabilityCheckNotification.SCOPE(NOTIFICATIONSCOPE::LocalScope); 
          -AvailabilityCheckNotification.ADDACTION(DetailsTxt,CODEUNIT::"Item-Check Avail.",'ShowNotificationDetails'); 
          -ItemAvailabilityCheck.PopulateDataOnNotification(AvailabilityCheckNotification,ItemNo,UnitOfMeasureCode,InventoryQty,GrossReq,ReservedReq,SchedRcpt,ReservedRcpt,CurrentQuantity,CurrentReservedQty,TotalQuantity,EarliestAvailDate); 
          -AvailabilityCheckNotification.SEND; 
          -EXIT(FALSE); 
          -END; 
          -
          -

          Now, notifications do not replace each other, but we cannot recall them because we do not track each notification ID.

          +
          LOCAL PROCEDURE CreateAndSendNotification@23(UnitOfMeasureCode@1010 : Code[20];InventoryQty@1009 : Decimal;GrossReq@1008 : Decimal;ReservedReq@1007 : Decimal;SchedRcpt@1006 : Decimal;ReservedRcpt@1005 : Decimal;CurrentQuantity@1004 : Decimal;CurrentReservedQty@1003 : Decimal;TotalQuantity@1002 : Decimal;EarliestAvailDate@1001 : Date) : Boolean; 
          +VAR 
          +  ItemAvailabilityCheck@1011 : Page 1872; 
          +  AvailabilityCheckNotification@1000 : Notification; 
          +BEGIN 
          +  AvailabilityCheckNotification.ID(CREATEGUID);
          +  AvailabilityCheckNotification.MESSAGE(STRSUBSTNO(NotificationMsg,ItemNo)); 
          +  AvailabilityCheckNotification.SCOPE(NOTIFICATIONSCOPE::LocalScope); 
          +  AvailabilityCheckNotification.ADDACTION(DetailsTxt,CODEUNIT::"Item-Check Avail.",'ShowNotificationDetails'); 
          +
          +  ItemAvailabilityCheck.PopulateDataOnNotification(AvailabilityCheckNotification,ItemNo,UnitOfMeasureCode,InventoryQty,GrossReq,ReservedReq,SchedRcpt,ReservedRcpt,CurrentQuantity,CurrentReservedQty,TotalQuantity,EarliestAvailDate); 
          +  AvailabilityCheckNotification.SEND; 
          +  EXIT(FALSE); 
          +END; 
          +

          Now, notifications do not replace each other, but we cannot recall them because we do not track each notification ID.

          Figure 3: Three sales lines with a notification for each one

          Imagine that you’re adding several items to a sales order, and inventory is low for some of the items. Each sales line will send a notification for its item if the quantity to sell is higher than the available inventory. By using dynamically generated notification IDs (CREATEGUID), each notification will not be overwritten, which is what we want. This is shown in Figure 3. But after you see the notification, you may decide to decrease the quantity in the sales line. At that point, the notification should be recalled. To do that, we need a way to track the notifications and their IDs.

          @@ -344,8 +345,6 @@ END;
        • RecallNotificationsForRecord(RecId : RecordID;HandleDelayedInsert : Boolean)

          -
        • -
          • Recalls all notifications that were sent by a given record ID. The HandleDelayedInsert flag should be TRUE if it is possible that the record ID provided is from a record that was not yet in the database (TRUE unless we recall notifications after deletion of a record).
          @@ -382,7 +381,6 @@ END;

          Figure 4: without additional context

          Figure 5: with additional context

          -

          __

          However, delayed insert means that the simple case seen above doesn’t happen very often. The issue is that when we call SendNotification, we provide the cause object’s record ID. If this object has not been inserted yet, which is often the case when the user creates a new invoice, a new line, and so on, the record ID is incomplete. When the object is inserted the record ID is completed, but if we call RecallNotificationsForRecord at a later point, the record ID will be different from the incomplete record ID we used when sending the notification. The solution is to detect that the object is not yet inserted when we send the notification, and at a later point, set the record ID when the cause object is inserted. 

          The realistic case is:

          @@ -402,8 +400,8 @@ END;

          COD312 (Cust-Check Cr. Limit)

          COD1508 (Notification Lifecycle Handler)

          Related Patterns:

          -

          In-context notifications (https://community.dynamics.com/nav/w/designpatterns/284.using-in-context-notifications)

          -

          Singleton codeunit (https://community.dynamics.com/nav/w/designpatterns/283.singleton-codeunit)

          +

          In-context notifications

          +

          Singleton codeunit

          @@ -416,12 +414,12 @@ END;
          - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/observer/index.html b/navpatterns/1-patterns/observer/index.html index fb148c08..a4938777 100644 --- a/navpatterns/1-patterns/observer/index.html +++ b/navpatterns/1-patterns/observer/index.html @@ -5,16 +5,16 @@ Observer :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
        - - - + + +
          @@ -268,8 +268,7 @@ ALGuidelines.Dev

        Observer

        -

        By Nikolai L’Estrange, from TVision Technology Ltd. in the UK -_

        +

        By Nikolai L’Estrange, from TVision Technology Ltd. in the UK

        Abstract

        Track all record changes against a defined table or set of tables.

        Problem

        @@ -278,39 +277,67 @@ _

        Create a setup table to define which other tables you want to track changes for, and optionally what triggers you want to fire, then link this up to the standard triggers in Codeunit 1.

        Codeunit 1 Application Management contains the triggers OnDatabaseInsert, OnDatabaseModify, OnDatabaseDelete and OnDatabaseRename which are what we need to subscribe to in order to track record changes. However these triggers are only fired sometimes. This is determined by the parameters set in the function GetTableTriggerSetup, which is called once per table per session.

        In order to define which tables we are interested in we can create a new table with the following fields:

        -
        **Observable Table:**
        -"Table ID" Integer Object.ID WHERE (Type=CONST(Table))
        -TrackInsert Boolean \[optional\]
        -TrackModify Boolean \[optional\]
        -TrackDelete Boolean \[optional\]
        -TrackRename Boolean \[optional\]
        -
        +

        Observable Table:

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        “Table ID”IntegerObject.ID WHERE (Type=CONST(Table))
        TrackInsertBoolean[optional]
        TrackModifyBoolean[optional]
        TrackDeleteBoolean[optional]
        TrackRenameBoolean[optional]

        Then we can create a Codeunit that will set the Table Trigger Setup parameters and also subscribe to the OnDatabase triggers.

        -
        **LOCAL \[EventSubscriber\] GetTableTriggerSetup(TableId : Integer;VAR OnDatabaseInsert : Boolean;VAR OnDatabaseModify : Boolean;VAR OnDatabaseDelete : Boolean;VAR OnDatabaseRename : Boolean)**  
        -IF Observable.GET(TableId) THEN BEGIN  
        -IF Observable.TrackInsert THEN  
        -OnDatabaseInsert := TRUE;  
        -IF Observable.TrackModify THEN  
        -OnDatabaseModify := TRUE;  
        -IF Observable.TrackDelete THEN  
        -OnDatabaseDelete := TRUE;  
        -IF Observable.TrackRename THEN  
        -OnDatabaseRename := TRUE;  
        -END;  
        -**LOCAL \[EventSubscriber\] OnDatabaseInsert(RecRef : RecordRef)**
        -IF Observable.Get(RecRef.NUMBER) AND Observable.TrackInsert THEN
        -//do something
        -**LOCAL \[EventSubscriber\] OnDatabaseModify(RecRef : RecordRef)**
        -IF Observable.Get(RecRef.NUMBER) AND Observable.TrackModify THEN
        -//do something
        -**LOCAL \[EventSubscriber\] OnDatabaseDelete(RecRef : RecordRef)**
        -IF Observable.Get(RecRef.NUMBER) AND Observable.TrackDelete THEN
        -//do something
        -**LOCAL \[EventSubscriber\] OnDatabaseRename(RecRef : RecordRef;xRecRef : RecordRef)**
        -IF Observable.Get(RecRef.NUMBER) AND Observable.TrackRename THEN
        -//do something
        -
        -

        Note: In NAV2016 all these functions can be EventSubscribers that subscribe to the functions in Codeunit 1 as per above, in earlier versions of NAV these functions will need to be Global and called explicitly from within the Codeunit 1 functions.

        +
        LOCAL [EventSubscriber] GetTableTriggerSetup(TableId : Integer;VAR OnDatabaseInsert : Boolean;VAR OnDatabaseModify : Boolean;VAR OnDatabaseDelete : Boolean;VAR OnDatabaseRename : Boolean)
        +IF Observable.GET(TableId) THEN BEGIN  
        +    IF Observable.TrackInsert THEN  
        +        OnDatabaseInsert := TRUE;  
        +    IF Observable.TrackModify THEN  
        +        OnDatabaseModify := TRUE;  
        +    IF Observable.TrackDelete THEN  
        +        OnDatabaseDelete := TRUE;  
        +    IF Observable.TrackRename THEN  
        +        OnDatabaseRename := TRUE;  
        +END;  
        +
        +LOCAL [EventSubscriber] OnDatabaseInsert(RecRef : RecordRef)
        +IF Observable.Get(RecRef.NUMBER) AND Observable.TrackInsert THEN
        +    //do something
        +
        +LOCAL [EventSubscriber] OnDatabaseModify(RecRef : RecordRef)
        +IF Observable.Get(RecRef.NUMBER) AND Observable.TrackModify THEN
        +    //do something
        +
        +LOCAL [EventSubscriber] OnDatabaseDelete(RecRef : RecordRef)
        +IF Observable.Get(RecRef.NUMBER) AND Observable.TrackDelete THEN
        +    //do something
        +
        +LOCAL [EventSubscriber] OnDatabaseRename(RecRef : RecordRef;xRecRef : RecordRef)
        +IF Observable.Get(RecRef.NUMBER) AND Observable.TrackRename THEN
        +    //do something
        +

        Note: In NAV2016 all these functions can be EventSubscribers that subscribe to the functions in Codeunit 1 as per above, in earlier versions of NAV these functions will need to be Global and called explicitly from within the Codeunit 1 functions.

        Variations of this pattern exists in the standard product in:

          @@ -339,12 +366,12 @@ IF Observable.Get(RecRef.NUMBER) AND Observable.TrackRename THEN
          - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/posting-routine-select-behavior/index.html b/navpatterns/1-patterns/posting-routine-select-behavior/index.html index a25207fd..1b0f34d1 100644 --- a/navpatterns/1-patterns/posting-routine-select-behavior/index.html +++ b/navpatterns/1-patterns/posting-routine-select-behavior/index.html @@ -5,16 +5,16 @@ Posting Routine - Select Behavior :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
            @@ -283,12 +283,12 @@ ALGuidelines.Dev

            Or in case of the Post&Print, the selection codeunit is different:

            Next, the user is able to select the options with an STRMENU, which results in filling in the fields above, like:

            -

            Selection := STRMENU(Text000,3);
            -IF Selection = 0 THEN
            -EXIT;
            -Ship := Selection IN [1,3];
            -Invoice := Selection IN [2,3];

            -

            The processing codeunit is being called after these options were set.

            +
            Selection := STRMENU(Text000,3);  
            +IF Selection = 0 THEN  
            +    EXIT;  
            +Ship := Selection IN [1,3];  
            +Invoice := Selection IN [2,3];
            +

            The processing codeunit is being called after these options were set.

            This is also being used in the Purchase Post.

            However, the Service Post works different. In Short:

            @@ -317,12 +317,12 @@ Invoice := Selection IN [2,3];

            - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/product-name/index.html b/navpatterns/1-patterns/product-name/index.html index 48502658..870e87f6 100644 --- a/navpatterns/1-patterns/product-name/index.html +++ b/navpatterns/1-patterns/product-name/index.html @@ -5,16 +5,16 @@ Product Name :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
          - - - + + +
            @@ -297,12 +297,12 @@ ALGuidelines.Dev
            - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/queries/index.html b/navpatterns/1-patterns/queries/index.html index 1df8f2f9..6de0bebe 100644 --- a/navpatterns/1-patterns/queries/index.html +++ b/navpatterns/1-patterns/queries/index.html @@ -6,16 +6,16 @@ Queries :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
          - - - + + +
            @@ -268,12 +268,12 @@ ALGuidelines.Dev
            - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/queries/index.xml b/navpatterns/1-patterns/queries/index.xml index 8b06cf22..70ba2620 100644 --- a/navpatterns/1-patterns/queries/index.xml +++ b/navpatterns/1-patterns/queries/index.xml @@ -2,7 +2,8 @@ Abstract This pattern explains how to perform SELECT DISTINCT by using queries in Dynamics NAV. Description When working with tables, sometimes a developer needs to perform a SELECT DISTINCT (also known as SELECT UNIQUE) from a table. As NAV does not provide this out of the box, we present below a way to select unique records by using queries. -Problem statementUse Queries to Detect Duplicate Recordshttps://alguidelines.dev/navpatterns/1-patterns/queries/use-queries-to-detect-duplicate-records/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/queries/use-queries-to-detect-duplicate-records/Originally by Abshishek Ghosh and Bogdan Sturzoiu at Microsoft Development Center Copenhagen +Problem statement +Let&rsquo;s consider the VAT Entry table as below:Use Queries to Detect Duplicate Recordshttps://alguidelines.dev/navpatterns/1-patterns/queries/use-queries-to-detect-duplicate-records/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/queries/use-queries-to-detect-duplicate-records/Originally by Abshishek Ghosh and Bogdan Sturzoiu at Microsoft Development Center Copenhagen Abstract This pattern uses queries to create an efficient way to detect duplicate entries in a table. This is, for example, useful when trying to find out which customers or contacts have the same names, so we can merge them later. Description Duplicate detection has several requirements in Microsoft Dynamics NAV. One method to eliminate duplication is by defining the relevant field as the primary key.Use Queries to Replace Nested Loopshttps://alguidelines.dev/navpatterns/1-patterns/queries/use-queries-to-replace-nested-loops/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/queries/use-queries-to-replace-nested-loops/Originally by Bogdan Sturzoiu, Microsoft Development Center Copenhagen Abstract This pattern shows how the new query object type introduced in NAV 2013 allows you to replace costly loops when inspecting data from two or more tables. diff --git a/navpatterns/1-patterns/queries/select-distinct-with-queries/index.html b/navpatterns/1-patterns/queries/select-distinct-with-queries/index.html index 09d613b1..08a9faa3 100644 --- a/navpatterns/1-patterns/queries/select-distinct-with-queries/index.html +++ b/navpatterns/1-patterns/queries/select-distinct-with-queries/index.html @@ -5,16 +5,16 @@ SELECT DISTINCT with Queries :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
          - - - + + +
            @@ -251,8 +251,8 @@ ALGuidelines.Dev
            • @@ -267,18 +267,15 @@ ALGuidelines.Dev

              SELECT DISTINCT with Queries

              By Bogdana Botez, at Microsoft Development Center Copenhagen

              -

              Abstract

              +

              Abstract

              This pattern explains how to perform SELECT DISTINCT by using queries in Dynamics NAV.

              -

              Description

              When working with tables, sometimes a developer needs to perform a SELECT DISTINCT (also known as SELECT UNIQUE) from a table. As NAV does not provide this out of the box, we present below a way to select unique records by using queries.

              -

              Problem statement

              -

              Let’s consider the VAT Entry table as below:
              -

              +

              Let’s consider the VAT Entry table as below:

              The goal is to select one line for each separate document that produced VAT Entries. In other words, we want records grouped by Type, Document Type and Document No.. However, if there are multiple lines with the same value of the triad Type, Document Type and Document No. in the VAT Entry table, we only want to see one of them.

              -

              Solution

              +

              Solution

              Create a new query object VAT Entry Distinct Document No., with a single DataItem sourced from VAT Entry table. Add the three desired group-by fields Type, Document Type and Document No. as columns.

              To enable grouping, add one more column, with Method Type = Totals. This will automatically set the Group By checkbox to TRUE on the three precedent fields.

              @@ -286,7 +283,7 @@ ALGuidelines.Dev

              Running the query yields a single record per document. You can notice in the second line below for example, how the sales invoice number 103001 had 2 VAT Entries, but it shows up only once in the query:

              -

              Example

              +

              Example

              One thing is to be noted: there is a limitation to how much information you can take out from the records. For example, if we need to extract more information than just the one we already have in the columns, then the following apply: adding one more column of Method Type = None will indeed show more information, but it might affect the grouping. More details below.

              The grouping is affected for example when the additional column is the VAT Entry No. In this case, this column brings up additional grouping criteria and one group for each entry number will end up being created.

              @@ -315,12 +312,12 @@ ALGuidelines.Dev
              - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/queries/use-queries-to-detect-duplicate-records/index.html b/navpatterns/1-patterns/queries/use-queries-to-detect-duplicate-records/index.html index b730c80e..eab673e5 100644 --- a/navpatterns/1-patterns/queries/use-queries-to-detect-duplicate-records/index.html +++ b/navpatterns/1-patterns/queries/use-queries-to-detect-duplicate-records/index.html @@ -5,16 +5,16 @@ Use Queries to Detect Duplicate Records :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
          - - - + + +
            @@ -249,13 +249,9 @@ ALGuidelines.Dev
            - - - + + +
            @@ -249,12 +249,12 @@ ALGuidelines.Dev

            When comparing the two implementations, we notice the following advantages of using a query instead of two loops:Comparison

            1. A query produces the Cartesian product of tables 1 and 2 faster than by looping through both of them. The advantage grows as there are more tables linked.
            @@ -430,9 +417,9 @@ END.
          • Using a query only requires one loop, whereas joining two or more tables requires multiple code loops that quickly become difficult to read and follow.
          • Queries are easy to create and maintain and generally provide a cleaner design.
      - +

      In Microsoft Dynamics NAV 2013 R2, we can see the query object used in the bank account reconciliation matching algorithm. The object is query Bank Rec. Match Candidates query (1252), and it is called by the matching engine in the Match Bank Rec. Lines codeunit (1252).

      -

      Ideas for Improvement

      +

      Ideas for Improvement

      The query object type could be improved to allow the passing of parameters at runtime, or, in general, being built dynamically at runtime. This will remove the need for multiple static definitions of the same base query used in slightly different contexts.

      @@ -446,12 +433,12 @@ END.
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/read-once-initialization-and-validation/index.html b/navpatterns/1-patterns/read-once-initialization-and-validation/index.html index 10e1e01d..9e7f657b 100644 --- a/navpatterns/1-patterns/read-once-initialization-and-validation/index.html +++ b/navpatterns/1-patterns/read-once-initialization-and-validation/index.html @@ -5,16 +5,16 @@ Read-once Initialization and Validation :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -248,13 +248,13 @@ ALGuidelines.Dev @@ -265,35 +265,32 @@ ALGuidelines.Dev

        Read-once Initialization and Validation

        By Tim Grant

        -

        Abstract

        +

        Abstract

        One time query and validation of a setup table.

        -
        -

        Description

        +

        Description

        Firstly identifies if a setup table record is in memory, removing the need to execute and validate the re-query again within a code unit. (From NAV 2013+ service tier database caching exists).

        If you need to query a setup table, but not sure if the table is yet in memory from earlier code, this helps to determine this call (see Singleton Table pattern).

        Allows a central function to validate critical fields before processing automated code. TESTFIELD calls are added to the function and not the INIT call in order to ensure that critical functions do not run without this human check in place as these may rely on key configuration. If you have a design where specific field values are critical (custom or standard) for integrity, then INIT INSERT is a dangerous default which could result in the corruption of processes. This is separate automated INIT INSERT from a setup page.

        - +

        A global variable record and a global status indicator, but within the context of a local function.

        - +

        A similar example of this is in Codeunit 80, but without any validation.

        E.g. If there is a bespoke change, then this existing GetGLSetup can be called earlier to accommodate for an earlier bespoke change:

        This means that by the time the code reaches the original GetGLSetup code requirement, this record is already in memory as the status flag is set to True, eliminating another check. For such Patterns the setup record should never be cleared, it should be called once globally, initiated and left in memory for future checks. It should not be declared locally at the same time, review your code for matching local variable usage before implementing this.

        -
        -

        Ideas for improvement

        +

        Ideas for improvement

        Merge Singleton Table with dynamic validation field meta configuration. This removes the coding of TESTFIELD to a central setup check.

        Example with the Sales & Receivables Setup

        -
        -

        Consequences

        +

        Consequences

        If in the circumstance that a field value in the setup table is expected to change within the life of the code component instance. e.g. If a CHANGECOMPANY is required to cross reference another Setup table, use a separate variable.

        - +

        This is related to the Singleton Table pattern.

        @@ -307,12 +304,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/released-entity/index.html b/navpatterns/1-patterns/released-entity/index.html index 86b22c88..2db5f0ea 100644 --- a/navpatterns/1-patterns/released-entity/index.html +++ b/navpatterns/1-patterns/released-entity/index.html @@ -5,16 +5,16 @@ Released Entity :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -248,8 +248,8 @@ ALGuidelines.Dev
        - - - + + +
        @@ -248,13 +248,13 @@ ALGuidelines.Dev @@ -268,15 +268,15 @@ ALGuidelines.Dev

        Group 1: Dale Gauci & Kimberly Congleton

        Group 2: Jens Winberg, Tim Grant, Alen Tot

        Thanks to Tim Grant who had merged the two patterns, corrected, sent for review and finalized them.

        -

        Meet the Pattern

        +

        Meet the Pattern

        It should be possible to change which document report object should be used when printing. This configuration can be stored in one place (Report Selection) and available at configuration time. In this situation, the code for printing records can remain the same.

        The purpose of this pattern is to describe the process to implement a reports selection by retrieving the specific Document Header, determining which type of document is related to it in the Report Selection Screen, and call the report which needs to be printed by passing the specific document header. This specifies the reports that you can print when you work with the various documents for sales and purchases, such as orders, quotes, invoices, and credit memos.

        -

        Know the Pattern

        +

        Know the Pattern

        The program can preselect which report will be printed when you print from various types of purchase and sales headers. For example, on an order, the Order Confirmation report is automatically printed.

        The Report Selection table contains the specification of which report will be printed in different situations. The Report Selection table also contains the report ID and report name for the report that will be printed when the user works with a given document type.

        The user can, of course, choose to have the program print a different report than the preselected one. You can also add reports to the Report Selection table to have the program print more than one report per document type.

        This pattern should be used when the user needs to print a new type of document which can have different report layouts, or when the user needs to print several different reports in sequence. This can also be used during a new post and print routine, a test report for journal posting or for re-printing a posted document. Using this pattern will minimize code by allowing a flexible means of specifying different reports to print, instead of hard-coding a report id when printing a document.

        -

        Use the Pattern

        +

        Use the Pattern

        The Report Selection Pattern involves the Report Selection table as the central configuration element also with the Report Selection Page. The table is used in 4 generic functional workflows:

        1. Test Print un-posted
        2. @@ -305,7 +305,7 @@ ALGuidelines.Dev
          • The specific document related posted header table is used along with a typical function: PrintRecords within the respective table.
          -

          Example

          +

          Example

          Sales Document (Invoice)

          Pre-conditions

            @@ -315,51 +315,46 @@ ALGuidelines.Dev

          Implementation:

            -
          • -

            Add a new Option String to the Report Selection table, Usage field (including any ML Captions).

            -
          • -
          • -

            Calling of GetReport function, passing the document header.

            -

            SalesSetup.GET; -IF SalesSetup.“Post & Print with ob Queue” THEN -SalesPostViaobQueue.EnqueueSalesDoc(SalesHeader) -ELSE BEGIN -CODEUNIT.RUN(CODEUNIT::“Sales-Post”,SalesHeader); -GetReport(SalesHeader); -END; -COMMIT;

            -
          • -
          • -

            Implementing the new document type within the GetReport Function, and calling PrintReport with the New OptionString

            -

            “Document Type”::Invoice: -BEGIN -IF “Last Posting No.” = '' THEN -SalesInvHeader.“No.":= “No.” -ELSE -SalesInvHeader.“No.” := “Last Posting No."; -SalesInvHeader.SETRECFILTER; -PrintReport(ReportSelection.Usage::“S.Invoice”); -END;

            -
          • -
          • -

            Implement the new Document Type and calling the associated report ID, passing the document header

            -
          • +
          • Add a new Option String to the Report Selection table, Usage field (including any ML Captions).
          • +
          • Calling of GetReport function, passing the document header.
          - +
          SalesSetup.GET;
          +IF SalesSetup."Post & Print with ob Queue" THEN
          +    SalesPostViaobQueue.EnqueueSalesDoc(SalesHeader)
          +ELSE BEGIN
          +    CODEUNIT.RUN(CODEUNIT::"Sales-Post",SalesHeader);
          +    GetReport(SalesHeader);
          +END;
          +COMMIT;
          +
            +
          • Implementing the new document type within the GetReport Function, and calling PrintReport with the New OptionString
          • +
          +
          "Document Type"::Invoice:
          +    BEGIN
          +        IF "Last Posting No." = '' THEN
          +            SalesInvHeader."No.":= "No."
          +        ELSE
          +            SalesInvHeader."No." := "Last Posting No.";
          +        SalesInvHeader.SETRECFILTER;
          +        PrintReport(ReportSelection.Usage::"S.Invoice");
          +    END;
          +
            +
          • Implement the new Document Type and calling the associated report ID, passing the document header
          • +
          +
          • Codeunits 82, 92, for post and printing purchase and sales documents
          • Codeunit 229 for Document printing
          • Header Tables 110, 112, 114, 120, 122, 124, 295, 297, 302, 304. 5744, 5746, 6650, 6660

          This pattern is already used for printing documents like sales invoices, proforma, waybills, Finance Charge Memos, Receipt Documents.

          -
          -

          Ideas for improvement

          +

          Ideas for improvement

          The “Usage” Field could be more dynamic through a setup field linked to a document type table mapped to the document header Table ID.

          Report Selection table is currently based on the Usage of the report to be defined. A more generic way could be to only select what record id the printing selection is based on could also be implemented. (I think this means Recordref and Table Number could be used to identify the posted tables)

          The Report Selection matrix could be evolved to accommodate the Journal post & print configuration. This setup currently resides in the Gen. Journal Template table.

          Merge the PrintReport functions in the purchase and sales post printing functions (82, 92) into a more atomic print selection component.

          Merge the PrintRecords functions in the separate posted header tables into a more atomic print selection component.

          - +
          • Posting Routine Pattern (Journal/Document)
          • Post Batch Routine Pattern
          • @@ -376,12 +371,12 @@ END;

            - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/security/1-sensitive-data-encapsulation/index.html b/navpatterns/1-patterns/security/1-sensitive-data-encapsulation/index.html index 600f4580..fda41420 100644 --- a/navpatterns/1-patterns/security/1-sensitive-data-encapsulation/index.html +++ b/navpatterns/1-patterns/security/1-sensitive-data-encapsulation/index.html @@ -5,16 +5,16 @@ Sensitive Data Encapsulation :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -256,17 +256,10 @@ ALGuidelines.Dev

        Sensitive Data Encapsulation

        By Bogdana Botez at Microsoft Development Center Copenhagen

        -

        _
        -_

        -

        **
        -**

        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.
        • @@ -276,16 +269,14 @@ _

        • 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 and Encryption.

        +

        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 and Encryption.

        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. -
        3. 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 pattern).
        4. +
        5. 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 pattern).

        Figure 1- Example definition, table used for Data Encapsulation

        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.

          @@ -303,46 +294,33 @@ _

        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.
        • +
        • 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 and logging can provide further protection.
        • +
        • 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 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]

        -

        waldo, “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].

        +

        [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] waldo, “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].

        @@ -355,12 +333,12 @@ _

        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/security/2-data-encryption/index.html b/navpatterns/1-patterns/security/2-data-encryption/index.html index fa542f00..e16439ce 100644 --- a/navpatterns/1-patterns/security/2-data-encryption/index.html +++ b/navpatterns/1-patterns/security/2-data-encryption/index.html @@ -5,16 +5,16 @@ Data Encryption :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -256,61 +256,86 @@ ALGuidelines.Dev

        Data Encryption

        By Bogdana Botez at Microsoft Development Center Copenhagen

        -

        __

        -

        _
        -_

        -
        -

        Context: After applying Sensitive Data Encapsulation, all sensitive data is gathered in a known place in the database. This makes it possible to apply further protection best practices.

        +

        +

        Context: After applying Sensitive Data Encapsulation, 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), 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.

        +

        Figure 1 adds one more step (in continuation of the figure in pattern Sensitive Data Encapsulation), 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.

        -

        Figure 1- Encryption becomes easier after first applying Sensitive Data Encapsulation.

        -
        +

        Figure 1- Encryption becomes easier after first applying Sensitive Data Encapsulation.

        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.

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        ProcedureDescription
        EnableEncryptionConfirms with the user before enabling encryption in all companies of the current database.
        EnableEncryptionSilentlyEnables encryption without UI interaction (to be used for web services).
        DisableEncryptionDisables encryption. Has a boolean “Silent” parameter which enables/disables UI interaction.
        EncryptChecks that encryption is possible before encrypting the given text.
        DecryptChecks that encryption is possible before decrypting the given text.
        ExportKeyIf 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.
        ImportKeyImports the encryption key from a user chosen location.
        ChangeKeyChanges the encryption key.
        IsEncryptionEnabledReturns TRUE is encryption is enabled.
        IsEncryptionPossibleCheck is the correct key is present, which only works if encryption is enabled.
        DeleteEncryptedDataInAllCompaniesConfirms 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.

        +
        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.

        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).

        @@ -340,34 +365,22 @@ _

        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 pattern to handle sensitive data which needs to 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 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]

        -

        waldo, “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].

        +

        [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] waldo, “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].

        @@ -380,12 +393,12 @@ _

        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/security/3-single-point-of-access/index.html b/navpatterns/1-patterns/security/3-single-point-of-access/index.html index df334303..bc68cb49 100644 --- a/navpatterns/1-patterns/security/3-single-point-of-access/index.html +++ b/navpatterns/1-patterns/security/3-single-point-of-access/index.html @@ -5,16 +5,16 @@ Single Point of Access :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -256,12 +256,9 @@ ALGuidelines.Dev

        Single Point of Access

        By Bogdana Botez at Microsoft Development Center Copenhagen

        -

        _
        -_

        -

        __

        +

        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.
        • @@ -273,8 +270,8 @@ _

      -

      Solution: In Dynamics NAV the same library which offers Encryption, 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), the panels 1, 2 and 3 have applied the Sensitive Data Encapsulation and Encryption 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.

      +

      Solution: In Dynamics NAV the same library which offers Encryption, 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), the panels 1, 2 and 3 have applied the Sensitive Data Encapsulation and Encryption 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.

      Figure 1- Single Point of Access pattern applied.

      @@ -296,30 +293,18 @@ _

    3. Single point of failure: A defect in the access library will affect all usages until it is found and fixed.
    4. 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]

      -

      waldo, “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].

      +

      [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] waldo, “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].

      @@ -332,12 +317,12 @@ _

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/security/4-masked-text/index.html b/navpatterns/1-patterns/security/4-masked-text/index.html index bb60f4bd..e4959d41 100644 --- a/navpatterns/1-patterns/security/4-masked-text/index.html +++ b/navpatterns/1-patterns/security/4-masked-text/index.html @@ -5,16 +5,16 @@ Masked Text :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
      - - - + + +
        @@ -256,9 +256,7 @@ ALGuidelines.Dev

        SSL in NAV

        By Bogdana Botez at Microsoft Development Center Copenhagen

        -

        _
        -_

        -

        __

        +

        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:

        @@ -269,7 +267,7 @@ _

        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.

        -

        ** **

        +

        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.

        Benefits:

        @@ -282,30 +280,18 @@ _

      • 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]

      -

      waldo, “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].

      +

      [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] waldo, “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].

      @@ -318,12 +304,12 @@ _

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/security/index.html b/navpatterns/1-patterns/security/index.html index bd2ea5f4..7f8d693c 100644 --- a/navpatterns/1-patterns/security/index.html +++ b/navpatterns/1-patterns/security/index.html @@ -6,16 +6,16 @@ Security :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -269,11 +269,11 @@ ALGuidelines.Dev

        When sensitive data is stored inside the Dynamics NAV database, if negligently handled, it can become vulnerable. Particularly, the location, access and the state of the data are critical.

        Use the following patterns to enhance security on your NAV system.

          -
        1. Sensitive Data Encapsulation
        2. -
        3. Data Encryption
        4. -
        5. Single Point of Access
        6. -
        7. Masked Text
        8. -
        9. SSL in NAV
        10. +
        11. Sensitive Data Encapsulation
        12. +
        13. Data Encryption
        14. +
        15. Single Point of Access
        16. +
        17. Masked Text
        18. +
        19. SSL in NAV
        @@ -287,12 +287,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/security/index.xml b/navpatterns/1-patterns/security/index.xml index cd8e3c0f..c978503e 100644 --- a/navpatterns/1-patterns/security/index.xml +++ b/navpatterns/1-patterns/security/index.xml @@ -1,36 +1,16 @@ Security on AL Guidelineshttps://alguidelines.dev/navpatterns/1-patterns/security/Recent content in Security on AL GuidelinesHugo -- gohugo.ioen-usSensitive Data Encapsulationhttps://alguidelines.dev/navpatterns/1-patterns/security/1-sensitive-data-encapsulation/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/security/1-sensitive-data-encapsulation/By Bogdana Botez at Microsoft Development Center Copenhagen -_ -_ -** -** 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.Data Encryptionhttps://alguidelines.dev/navpatterns/1-patterns/security/2-data-encryption/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/security/2-data-encryption/By Bogdana Botez at Microsoft Development Center Copenhagen -__ -_ -_ Context: After applying Sensitive Data Encapsulation, 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.Single Point of Accesshttps://alguidelines.dev/navpatterns/1-patterns/security/3-single-point-of-access/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/security/3-single-point-of-access/By Bogdana Botez at Microsoft Development Center Copenhagen -_ -_ -__ 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.Masked Texthttps://alguidelines.dev/navpatterns/1-patterns/security/4-masked-text/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/security/4-masked-text/By Bogdana Botez at Microsoft Development Center Copenhagen -_ -_ -__ 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.SSL in NAVhttps://alguidelines.dev/navpatterns/1-patterns/security/5-ssl-in-nav/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/security/5-ssl-in-nav/By Bogdana Botez at Microsoft Development Center Copenhagen -_ -_ -__ 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&rsquo;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. \ No newline at end of file diff --git a/navpatterns/1-patterns/setup-specificity-fallback/index.html b/navpatterns/1-patterns/setup-specificity-fallback/index.html index dde842af..1acc08d3 100644 --- a/navpatterns/1-patterns/setup-specificity-fallback/index.html +++ b/navpatterns/1-patterns/setup-specificity-fallback/index.html @@ -5,16 +5,16 @@ Setup Specificity Fallback :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -248,10 +248,10 @@ ALGuidelines.Dev @@ -262,40 +262,51 @@ ALGuidelines.Dev

        Setup Specificity Fallback

        By Jan Hoek at IDYN

        -

        Abstract

        +

        Abstract

        The Setup Specificity Fallback pattern allows users to efficiently define a potentially complex setup in terms of rules and exceptions to these rules, exceptions to the exceptions, etc.

        -

        Description

        +

        Description

        The pattern involves a setup table with a compound (i.e. consisting of more than one field) primary key, where each record in the table maps a combination of primary key values to a particular setup value. However, setting up and maintaining each and every combination could prove to be rather labour-intensive.

        With the Setup Specificity Pattern in place, primary key fields in the setup table can have a special value (typically: zero or an empty string) that acts as a wildcard, meaning that the setup record in question applies to a combination of primary key fields with any value in the field containing that wildcard. According to the pattern, when querying the setup table, the application attempts to find the appropriate setup record by searching less and less specific, starting with only specified primary key values, and possibly ending with only wildcard values, replacing specific values with wildcards in a predefined order until a setup record is found, e.g.:

        -

        Step 1.

        -

        Try to find record with:
        -Field A = value “a”
        -Field B = value “b” -If found, return record; if not, go to step 2. -Step 2.

        -

        Try to find record with:
        -Field A = value “a”
        -Field B = wildcard value -If found, return record; if not, go to step 3. -Step 3. -Try to find record with:
        -Field A = wildcard value
        -Field B = value “b” -If found, return record; if not, go to step 4. -Step 4. -Try to find record with:
        -Field A = wildcard value
        -Field B = wildcard value
        -If found, return record; if not, optionally return hard-coded value

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        Step 1.Try to find record with: Field A = value “a” and Field B = value “b”If found, return record; if not, go to step 2.
        Step 2.Try to find record with: Field A = value “a” and Field B = wildcard valueIf found, return record; if not, go to step 3.
        Step 3.Try to find record with: Field A = wildcard value and Field B = value “b”If found, return record; if not, go to step 4.
        Step 4.Try to find record with: Field A = wildcard value and Field B = wildcard valueIf found, return record; if not, optionally return hard-coded value

        The least specific record can be thought of as the general rule, and each of the subsequent records is an exception to that rule. By defining only exceptions to the rule, instead of each and every combination, the burden of setting up and maintaining the setup records is strongly reduced, and the overall setup is much easier to read and understand.

        -

        Usage

        +

        Usage

        1. Create your setup table. Remember that this pattern applies only to setup tables with a compound primary key;
        2. Create a function that will return the required value from the setup;
        3. For each level of specificity, the function should test if the corresponding record exists. If it does, the setup value should be returned. If it doesn’t, the logic should fall-back to a lower level of specificity.
        4. (Optional) If even the least specific setup record can not be found, the function may return a blank value or hard-coded default value.
        - +

        The Printer Selection table in NAV is an example of the Setup Specificity Fallback pattern. Its primary key consists of two fields. A blank value in either field means that the record applies to any user or report respectively.

        The FindPrinter function in codeunit 1 searches for the printer to use, with a decreasing level of specificity.

        @@ -312,12 +323,12 @@ If found, return record; if not, optionally return hard-coded value

        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/silent-file-upload-and-download/index.html b/navpatterns/1-patterns/silent-file-upload-and-download/index.html index 1058e6e9..75fb8d02 100644 --- a/navpatterns/1-patterns/silent-file-upload-and-download/index.html +++ b/navpatterns/1-patterns/silent-file-upload-and-download/index.html @@ -5,16 +5,16 @@ Silent File Upload and Download :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -279,13 +279,15 @@ ALGuidelines.Dev

        Sometimes, files must be transferred to or from known locations without triggering file-save or file-load dialogs.

        In the following, both the historical and the recommended ways of silently transferring files are described. Since we keep both implementations possible for the sake of backward compatibility, we strongly recommend that you use the file-transfer API provided that is provided with the File Management codeunit (419).

        The legacy API for file transfers [2]:

        -

        [Ok :=] UPLOAD(DialogTitle, FromFolder, FromFilter, FromFile, ToFile)

        -

        [Ok :=] DOWNLOAD(FromFile, DialogTitle, ToFolder, ToFilter, ToFile)

        -

        As you can see, this historical API leaves no place for turning off the functionality for showing a dialog. Historically, NAV offered a remedy to this, namely by using the “Magicpath” string, which is the constant ‘<TEMP>’. Under this condition, the way to invoke silent file upload or download becomes:

        -

        [Ok :=] UPLOAD(DialogTitle, Magicpath, FromFilter, FromFile, ToFile)

        -

        [Ok :=] DOWNLOAD(FromFile, DialogTitle, Magicpath, ToFilter, ToFile)

        -

        This remedy introduced an issue: If we use “Magicpath” instead of FromFolder and ToFolder specifications, then where do we upload from and where do we download to? The answer is that they are uploaded to and downloaded from the NAV server’s temporary folder. The path to the temporary file can be obtained when this file is created, by using the following function in File Management: <tempFileName> := ServerTempFileName(<fileExtension>).

        -

        The new API for file transfers in the **File Management **codeunit:

        +
        [Ok :=] UPLOAD(DialogTitle, FromFolder, FromFilter, FromFile, ToFile) 
        +
        +[Ok :=] DOWNLOAD(FromFile, DialogTitle, ToFolder, ToFilter, ToFile)
        +

        As you can see, this historical API leaves no place for turning off the functionality for showing a dialog. Historically, NAV offered a remedy to this, namely by using the “Magicpath” string, which is the constant ‘<TEMP>’. Under this condition, the way to invoke silent file upload or download becomes:

        +
        [Ok :=] UPLOAD(DialogTitle, Magicpath, FromFilter, FromFile, ToFile) 
        +
        +[Ok :=] DOWNLOAD(FromFile, DialogTitle, Magicpath, ToFilter, ToFile)
        +

        This remedy introduced an issue: If we use “Magicpath” instead of FromFolder and ToFolder specifications, then where do we upload from and where do we download to? The answer is that they are uploaded to and downloaded from the NAV server’s temporary folder. The path to the temporary file can be obtained when this file is created, by using the following function in File Management: <tempFileName> := ServerTempFileName(<fileExtension>).

        +

        The new API for file transfers in the File Management codeunit:

        [Text :=] UploadFileSilent(ClientFilePath)

        [Text :=] DownloadTempFile(ServerFileName)

        Using the API in the File Management codeunit instead of the historical API is recommended for all file transferring and file management in NAV implementations.

        @@ -297,15 +299,17 @@ ALGuidelines.Dev

        The production manager wants to have the file in a predefined location on his hard drive. The location has been defined in a NAV setup table.

        The NAV Developer

        The NAV developer has written a module to query the CRONUS database and to export the product list in the pre-described XML format required by the web shop. He saves the data in a temporary server file created with this code:

        -

        ServerFileName := FileManagement.ServerTempFileName(‘xml’);

        -

        When the file has been populated with the latest product data, the NAV developer uses the following call to download the file from the temporary location on the server to the predefined location on the client:

        -

        FileManagement.DownloadToFile(ServerFileName,ClientFileName);

        -

        The call to DownloadToFile is part of the **File Management **codeunit, and it embeds the silent download offered by DownloadTempFile:

        -

        PROCEDURE DownloadToFile@13(ServerFileName@1002 : Text;ClientFileName@1000 : Text);

        -

        VAR TempClientFileName@1001 : Text;

        -

        BEGIN ValidateFileNames(ServerFileName,ClientFileName); TempClientFileName := DownloadTempFile(ServerFileName); MoveFile(TempClientFileName,ClientFileName);

        -

        END

        -

        Consequences

        +
        ServerFileName := FileManagement.ServerTempFileName('xml');
        +

        When the file has been populated with the latest product data, the NAV developer uses the following call to download the file from the temporary location on the server to the predefined location on the client:

        +
        FileManagement.DownloadToFile(ServerFileName,ClientFileName);
        +

        The call to DownloadToFile is part of the File Management codeunit, and it embeds the silent download offered by DownloadTempFile:

        +
        PROCEDURE DownloadToFile@13(ServerFileName@1002 : Text;ClientFileName@1000 : Text);
        +VAR
        +    TempClientFileName@1001 : Text;
        +BEGIN
        +    ValidateFileNames(ServerFileName,ClientFileName); TempClientFileName := DownloadTempFile(ServerFileName); MoveFile(TempClientFileName,ClientFileName);
        +END
        +

        Consequences

        1. Even today, there is no fully silent up or download. The user will get a warning stating the system is trying to run a client side .NET component. However when dismissing this warning, the user can show to not see it again.
        2. Due to client side .NET interop and of security concerns, silently downloading/uploading files on the web is disabled. Therefore, this pattern is recommended for implementation with Windows clients only.
        3. @@ -322,12 +326,12 @@ ALGuidelines.Dev
          - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/singleton/index.html b/navpatterns/1-patterns/singleton/index.html index d6b0b29a..dff566f1 100644 --- a/navpatterns/1-patterns/singleton/index.html +++ b/navpatterns/1-patterns/singleton/index.html @@ -6,16 +6,16 @@ Singleton :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -264,7 +264,7 @@ ALGuidelines.Dev
      • Lost reference to centralizer: An instance of a relevant object could attempt to centralize control or data in the system. However, once this object is no longer in scope, the reference to it is lost and cannot be retrieved for later coordination
      • Cannot rebuild the initial state: A new instance of the same object could be created, however the last known state of the lost instance cannot be known anymore.
      -

      Solution: create an object which resides in memory in a single copy (instance). Have a way to retrieve this unique object from code. This object can either be a Singleton Table, or a Singleton Codeunit.

      +

      Solution: create an object which resides in memory in a single copy (instance). Have a way to retrieve this unique object from code. This object can either be a Singleton Table, or a Singleton Codeunit.

      The implementation varies depending on the technology and language used. In object-oriented (OO) languages like C#, C++ or Java, the Singleton uses static classes and class members to instantiate. There is a mechanism to ensure only one instance of the object can exist. This mechanism is many times explicit (like in OO languages) and sometimes implicit as part of the compiler or platform (as it is in NAV).

      Benefits:

        @@ -284,12 +284,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/singleton/index.xml b/navpatterns/1-patterns/singleton/index.xml index 8fce1633..e864d2a9 100644 --- a/navpatterns/1-patterns/singleton/index.xml +++ b/navpatterns/1-patterns/singleton/index.xml @@ -1,6 +1,4 @@ Singleton on AL Guidelineshttps://alguidelines.dev/navpatterns/1-patterns/singleton/Recent content in Singleton on AL GuidelinesHugo -- gohugo.ioen-usSingleton Tablehttps://alguidelines.dev/navpatterns/1-patterns/singleton/singleton-table/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/singleton/singleton-table/Singleton Table By Elly Nkya at Microsoft Development Center Copenhagen -_ -_ Problem: The developer needs to define a single record that can contain a set of rules and behavior (optional, mandatory, or defaulting mechanisms), that apply to a functionality, and can be configured by a user. Forces You want a central place to define the address and logo of your company (see Company Information table). You want to define the no.Singleton Codeunithttps://alguidelines.dev/navpatterns/1-patterns/singleton/singleton-codeunit/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/1-patterns/singleton/singleton-codeunit/By Bogdana Botez at Microsoft Development Center Copenhagen diff --git a/navpatterns/1-patterns/singleton/singleton-codeunit/index.html b/navpatterns/1-patterns/singleton/singleton-codeunit/index.html index 5c345605..72b165b8 100644 --- a/navpatterns/1-patterns/singleton/singleton-codeunit/index.html +++ b/navpatterns/1-patterns/singleton/singleton-codeunit/index.html @@ -5,16 +5,16 @@ Singleton Codeunit :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -267,25 +267,26 @@ ALGuidelines.Dev

        Take for example the following test of Azure ML (Azure Machine Learning) integration with NAV.

        Context: Azure Machine Learning services are paid. However, Dynamics NAV includes a monthly pre-paid quota of Azure ML, which can be used for free by the users. There is an upper limit to this quota, and when it is reached, Azure ML services are turned off until the next month starts and a new quota becomes available for consumption.

        The test below checks that, when the monthly quota is exceeded, then the function IsAzureMLLimitReached returns TRUE.

        -
        \[Test\]
        -PROCEDURE AzureMLProcessingTimeExceedsLimit@11();
        -VAR  
        -AzureMachineLearningUsage@1004 : Record 2002;
        -ProcessingTime@1000 : Decimal;  
        -BEGIN
        -// \[SCENARIO\] Azure Machine Learning Processing time exceeds AzureML limit  
        -// \[GIVEN\] AzureMachineLearningUsage \> 0
        -Initialize; // calls PermissionManager.SetTestabilitySoftwareAsAService(TRUE);
        -ProcessingTime := LibraryRandom.RandDec(1000,2);
        -AzureMachineLearningUsage.IncrementTotalProcessingTime(ProcessingTime);  
        -// \[WHEN\] When IsAzureMLLimitReached is invoked with Limit more than Processing time
        -// \[THEN\] HasAzureLimitReached returns TRUE
        -Assert.IsTrue(AzureMachineLearningUsage.IsAzureMLLimitReached(ProcessingTime - 1),
        -'HasAzureLimitReached returns wrong value when Processing time exceeds Limit.');
        -PermissionManager.SetTestabilitySoftwareAsAService(FALSE);  
        -END;
        -
        -

        The figure below explains what happens when the Permission Manager is not a singleton. When it is invoked from different places (first from the test, second from the production code), then different instances of the Permission Manager will fire up and answer. In detail:

        +
        [Test]
        +PROCEDURE AzureMLProcessingTimeExceedsLimit@11();
        +VAR  
        +    AzureMachineLearningUsage@1004 : Record 2002;
        +    ProcessingTime@1000 : Decimal;  
        +BEGIN
        +    // [SCENARIO] Azure Machine Learning Processing time exceeds AzureML limit  
        +
        +    // [GIVEN] AzureMachineLearningUsage > 0
        +    Initialize; // calls PermissionManager.SetTestabilitySoftwareAsAService(TRUE);
        +    ProcessingTime := LibraryRandom.RandDec(1000,2);
        +    AzureMachineLearningUsage.IncrementTotalProcessingTime(ProcessingTime);
        +
        +    // [WHEN] When IsAzureMLLimitReached is invoked with Limit more than Processing time
        +    // [THEN] HasAzureLimitReached returns TRUE
        +    Assert.IsTrue(AzureMachineLearningUsage.IsAzureMLLimitReached(ProcessingTime - 1),
        +        'HasAzureLimitReached returns wrong value when Processing time exceeds Limit.');
        +    PermissionManager.SetTestabilitySoftwareAsAService(FALSE);  
        +END;
        +

        The figure below explains what happens when the Permission Manager is not a singleton. When it is invoked from different places (first from the test, second from the production code), then different instances of the Permission Manager will fire up and answer. In detail:

        1. The test calls Initialize which sets SaaS=TRUE in codeunit Permission Manager
        2. The test calls into production code to validate it works as expected. It calls AzureMachineLearningUsage codeunit to find out if the monthly quota has been reached. The function IsAzureMLLimitReached in AzureMachineLearningUsage codeunit is designed only for SaaS. If the code doesn’t run in SaaS, then it always returns FALSE.
        3. @@ -294,13 +295,11 @@ END;
        4. The production code will assess (wrongly) that it’s not running SaaS, and say that the Azure ML limit has not been reached (incorrect – and the test fails).

        -

        Solution: restrict the number of instantiations of a codeunit to only one, by setting the codeunit property SingleInstance to Yes.

        Returning to the previous example, let’s analyze the case when the codeunit Permission Manager is a singleton codeunit:

        When the codeunit Permission Manager is a singleton, then no matter from where it is invoked, the same instance will be reached. Therefore, the status set by the test (SaaS = TRUE) will be reachable from the production code, and the test will pass, as seen in the figure below.

        -

        **
        -**

        +

        Consequences

        1. Use Singleton Codeunit with care and only when there is no other solution. Preserving a global state could often enough be more harmful than useful. One risk is that tests might fail apparently non-deterministically.

        For example, a problem we have met in the development team for Madeira release, was that the singleton codeunit function PermissionManager.SetSoftwareAsAService(TRUE) is often used to emulate and test SaaS conditions. However, if a test ‘forgets’ to reset the state to default (FALSE), then another codeunit which is not supposed to emulate SaaS, will suddenly run as SaaS and will fail. Even if the test has code that resets the state to FALSE, this code might never be reached because of an earlier failure or other error in the test which would stop execution.

        @@ -318,7 +317,6 @@ END;
      • Etc.

      Note: while the object-oriented Singleton pattern can restrict the number of instantiations of the singleton to an integer n > 0, in Dynamics NAV the Singleton Codeunit can only have n=1.

      -

      __

      @@ -331,12 +329,12 @@ END;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/singleton/singleton-table/cue-table/index.html b/navpatterns/1-patterns/singleton/singleton-table/cue-table/index.html index 58625bce..f5ef71c9 100644 --- a/navpatterns/1-patterns/singleton/singleton-table/cue-table/index.html +++ b/navpatterns/1-patterns/singleton/singleton-table/cue-table/index.html @@ -5,16 +5,16 @@ Cue Table :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -258,72 +258,112 @@ ALGuidelines.Dev

        Cue Table

        By Bogdana Botez at Microsoft Development Center Copenhagen

        -

        Cues are the second usual application of the Singleton Table pattern in Dynamics NAV, after Setup Tables.

        +

        Cues are the second usual application of the Singleton Table pattern in Dynamics NAV, after Setup Tables.

        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.

        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. -
        3. By writing C/AL code to perform custom calculations. Use this when:
        4. -
        -
        • +

          By using a FlowField. This applies for simpler calculations, like filtered or unfiltered counts, sums etc.

          +
        • +
        • +

          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 and in Walkthrough: Creating a Cue Based on a FlowField.

    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 IDTable Name
    1313Activities Cue
    5370CRM Synch. Job Status Cue
    9042Team Member Cue
    9050Warehouse Basic Cue
    9051Warehouse WMS Cue
    9052Service Cue
    9053Sales Cue
    9054Finance Cue
    9055Purchase Cue
    9056Manufacturing Cue
    9057Job Cue
    9058Warehouse Worker WMS Cue
    9059Administration Cue
    9060SB Owner Cue
    9061RapidStart Services Cue
    9063Relationship Mgmt. Cue
    9069O365 Sales Cue
    9070Accounting Services Cue

    Table 1 - Cue tables in Dynamics NAV

    -
    -

    _

    @@ -336,12 +376,12 @@ ALGuidelines.Dev
    - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/singleton/singleton-table/index.html b/navpatterns/1-patterns/singleton/singleton-table/index.html index 6fd86f1f..2b730ddb 100644 --- a/navpatterns/1-patterns/singleton/singleton-table/index.html +++ b/navpatterns/1-patterns/singleton/singleton-table/index.html @@ -6,16 +6,16 @@ Singleton Table :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev - - - + + +
    @@ -263,11 +262,8 @@ ALGuidelines.Dev

    Singleton Table

    Singleton Table

    By Elly Nkya at Microsoft Development Center Copenhagen

    -

    -

    _
    -_

    +

    Problem: The developer needs to define a single record that can contain a set of rules and behavior (optional, mandatory, or defaulting mechanisms), that apply to a functionality, and can be configured by a user.

    -

    Forces

    • You want a central place to define the address and logo of your company (see Company Information table).
    • @@ -282,43 +278,38 @@ _

      2. Instantiate: Place the instantiation code in a central place where it is guaranteed to be invoked before the functionality uses it. This is done in Codeunit 2.

      3. Enforce: Give the user access to the record so that he can change the default setup, by creating a Card page. On the page, enforce the singleton to prevent deletion of the record or insertion of a new record

      4. Use: Access the rule in code and use it

      -

      NAV Usages

      Rounding rules for Unit-Amounts and Amounts are implemented using the Singleton pattern.

      1. Define: The General Ledger Setup is used for this.

      2. Instantiate: In codeunit 2, the following code is invoked

      -
      WITH GLSetup DO
      -IF NOT FINDFIRST THEN BEGIN
      -INIT;
      -INSERT;
      -END;
      -
      -

      3. Enforce: On the General Ledger Setup. The following properties are setup:

      -
      DeleteAllowed=false,
      -InsertAllowed=false
      -
      -

      4. Use: Access the rounding rules are used

      -
      ...
      -GLSetup.GET;
      -UnitCostCurrency := ROUND(...,GLSetup."Unit-Amount Rounding Precision"); 
      -... 
      -
      -

      Or if accessing the rule multiple times and performance is a consideration, use lazy instantiation:

      -
      ...
      -GetGLSetup;
      -UnitCostCurrency := ROUND(...,GLSetup."Unit-Amount Rounding Precision");
      -...
      -
      -LOCAL GetGLSetup()
      -IF NOT GLSetupRead THEN
      -GLSetup.GET;
      -GLSetupRead := TRUE;
      -
      -

      Related topics

      +
      WITH GLSetup DO
      +    IF NOT FINDFIRST THEN BEGIN
      +        INIT;
      +        INSERT;
      +    END;
      +

      3. Enforce: On the General Ledger Setup. The following properties are setup:

      +
          DeleteAllowed=false,
      +    InsertAllowed=false
      +

      4. Use: Access the rounding rules are used

      +
      ...
      +GLSetup.GET;
      +UnitCostCurrency := ROUND(...,GLSetup."Unit-Amount Rounding Precision"); 
      +... 
      +

      Or if accessing the rule multiple times and performance is a consideration, use lazy instantiation:

      +
      ...
      +GetGLSetup;
      +UnitCostCurrency := ROUND(...,GLSetup."Unit-Amount Rounding Precision");
      +...
      +
      +LOCAL GetGLSetup()
      +IF NOT GLSetupRead THEN
      +    GLSetup.GET;
      +GLSetupRead := TRUE;
      +

      Related topics

      Singleton design pattern.

      The Singleton Table has two established applications in Dynamics NAV:

        -
      1. Setup Tables – which are commonly storing user setup data in NAV,
      2. +
      3. Setup Tables – which are commonly storing user setup data in NAV,
      4. Cue Tables – used to calculate values for the visual representation of Cues on the NAV role center pages.

      YouTube Video of NAV Singleton:

      @@ -337,12 +328,12 @@ GLSetupRead := TRUE;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/singleton/singleton-table/setup-table/index.html b/navpatterns/1-patterns/singleton/singleton-table/setup-table/index.html index 9f814a06..367cf710 100644 --- a/navpatterns/1-patterns/singleton/singleton-table/setup-table/index.html +++ b/navpatterns/1-patterns/singleton/singleton-table/setup-table/index.html @@ -5,16 +5,16 @@ Setup Table :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -274,14 +274,13 @@ ALGuidelines.Dev

        Creating a Page

        The CardPage type is most suitable for representing this kind of tables. In addition, the InsertAllowed and DeleteAllowed properties in the page should be set to false to prevent the user from adding or deleting records in the table.

        In the OnOpenPage trigger, the following code should be added to insert a record when the user opens the page for the first time, if a record does not exist already.

        -
        OnOpenPage()
        -RESET;
        -IF NOT GET THEN BEGIN
        -INIT;
        -INSERT;
        -END;
        -
        -

        The following diagram describes the flow of the program, once the user tries to access the setup information. The user opens the page. If the record containing setup information already exists, then the page opens on the existing record. Else, a new empty record is created and the page opens on it.

        +
        OnOpenPage()
        +    RESET;
        +    IF NOT GET THEN BEGIN
        +        INIT;
        +        INSERT;
        +    END;
        +

        The following diagram describes the flow of the program, once the user tries to access the setup information. The user opens the page. If the record containing setup information already exists, then the page opens on the existing record. Else, a new empty record is created and the page opens on it.

        Company-Initialize Codeunit

        The Company-Initialize codeunit (codeunit 2) is executed when a new company is created. We recommended that you add records to the single-record tables in this codeunit. If some of the fields are expected to have default values, they can also be populated here.

        @@ -308,12 +307,12 @@ END;
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/standard-journal/index.html b/navpatterns/1-patterns/standard-journal/index.html index ebbb27b0..3ef6ed71 100644 --- a/navpatterns/1-patterns/standard-journal/index.html +++ b/navpatterns/1-patterns/standard-journal/index.html @@ -5,16 +5,16 @@ Standard Journal :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -314,12 +314,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/temporary-dataset-report/0250.Temporary-Dataset-Report-1.png b/navpatterns/1-patterns/temporary-dataset-report/0250.Temporary-Dataset-Report-1.png new file mode 100644 index 0000000000000000000000000000000000000000..67260f5c62a36b7ba160d71510a5607667287e78 GIT binary patch literal 46629 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 literal 0 HcmV?d00001 diff --git a/navpatterns/1-patterns/temporary-dataset-report/2376.Temporary-Dataset-Report-2.png b/navpatterns/1-patterns/temporary-dataset-report/2376.Temporary-Dataset-Report-2.png new file mode 100644 index 0000000000000000000000000000000000000000..d536ba9796b641fdd7f0146d46c934296b507c2e GIT binary patch literal 85238 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 literal 0 HcmV?d00001 diff --git a/navpatterns/1-patterns/temporary-dataset-report/7607.Temporary-Dataset-Report-3.png b/navpatterns/1-patterns/temporary-dataset-report/7607.Temporary-Dataset-Report-3.png new file mode 100644 index 0000000000000000000000000000000000000000..8aa6399b80a644496df3e1fe149697e8851bbb70 GIT binary patch literal 5708 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 yqLFw$tLB{iF~_|M Temporary Dataset Report :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -250,7 +250,11 @@ ALGuidelines.Dev @@ -264,7 +268,7 @@ ALGuidelines.Dev

        Originally by Abhishek Ghosh, at Microsoft Development Center Copenhagen

        Abstract

        This pattern generates the data to be displayed dynamically by combing/processing several data sources. It then displays the resulting dataset without writing to the database.

        -

        +

        Description

        While writing reports in NAV, we have the luxury of using a built-in iterator. So, once we define the dataitem and the ordering, the runtime takes care of the iteration.

        The iterator has one shortcoming: It can only run through records written into the database. There are situations, however, where we want to display temporary datasets created at runtime by processing data from different sources. That is where the Temporary Dataset Report pattern can be used.

        @@ -278,23 +282,37 @@ ALGuidelines.Dev

        Iterate through a dataitem of the Integer table and display one record from the temporary recordset in each iteration.

      -

      Step 1: Combining data sources to create a dataset

      +

      Step 1: Combining data sources to create a dataset

      In this step, we would process the existing data to create a temporary recordset. The three most common techniques to do this are discussed in the following paragraphs.

      The first technique is mostly used when we want to build the report based on one or more source tables. A lot of processing is required, and we therefore want to store and display information from a temporary recordset. With this technique, we create a dataitem of the source record and then iterate through this dataitem to create the temporary recordset. An advantage of this technique is that it allows the user to perform additional filtering on the data source tables since they are added as additional dataitems and therefore will have their tabs on the request page by default.

      -

      +

      The second technique was made available with NAV 2013 when queries were introduced as a tool to help us combine data from different sources. Instead of writing data into a temporary record variable, we can create a query to combine the data from different sources. This offers better performance than the first technique in almost every situation. However, with a query, we sacrifice the luxury of getting a flexible filtering on the request page for the source dataitem.

      The third technique is to write a function (or a codeunit, if the complexity demands so) that will crunch the data and create the temporary record variable. This function must be invoked from the OnPreReport trigger (or the OnPreDataItem trigger of the Integer dataitem).

      -

      +

      The following table summarizes when to use each of the three techniques:

      -

      Technique

      -

      When to Use

      -

      Source Record DataItem

      -

      When it is important to offer flexible filtering possibilities on the source data.

      -

      Query

      -

      When performance is critical.

      -

      Populating temporary table in a function

      -

      Only when the source dataset is too complex to use either of the other techniques.

      -

      Step 2: Iterating through the Integer dataitem

      + + + + + + + + + + + + + + + + + + + + + +
      TechniqueWhen to Use
      Source Record DataItemWhen it is important to offer flexible filtering possibilities on the source data.
      QueryWhen performance is critical.
      Populating temporary table in a functionOnly when the source dataset is too complex to use either of the other techniques.
      +

      Step 2: Iterating through the Integer dataitem

      When you have created the dataset as a temporary record variable or a query, the next step is to iterate through them to display the information. However, the report controller in NAV cannot iterate through temporary records or through the results of a query. This is where the Integer table comes into use.

      The Integer table is a virtual table with only one field, Number, which is an integer field. For all practical purposes, we can assume that this table has pre-inserted records with value of Number, starting from 1 and ending with the highest integer value. So, if we keep iterating through this dataitem, the value of the Number field will be 1 for the first iteration and will keep increasing by 1 for every iteration.

      There are two ways to use the Integer dataitem to iterate through the temporary recordset:

      @@ -309,9 +327,9 @@ ALGuidelines.Dev

      With the first approach, in the Integer-OnAfterGetRecord trigger, you must make the temporary record variable move to the next record, follow that up with a check if you have reached the end of the recordset, and then use CurrReport.BREAK if you have reached the end.

      With the second approach, you must limit the number of times the report iterates through the Integer dataitem. The Number field in the Integer table will have a value of 1 in the first iteration and will increment automatically by 1 on every subsequent iteration. To ensure that the report iterates through the Integer dataitem “n” times, you must, therefore, filter the Integer dataitem on Number = 1..n. You must also move the temporary record variable to the next record in every iteration of the Integer-OnAfterGetRecord trigger. NOTE: This technique cannot be used if you use queries as the temporary data source, since queries do not (in NAV 2013) return the number of records.

      Example

      -

      Goal: To print a report that lists all contacts that have open documents (both sales and purchase) and displays those documents in chronological order

      +

      Goal: To print a report that lists all contacts that have open documents (both sales and purchase) and displays those documents in chronological order

      A contact can be connected to a vendor or a customer using the Create As Customer/Vendor function on the Contact Card. When a sales/purchase document is created for this customer/vendor, the Contact No. is stored in the Sell-to Contact No. or Buy-from Contact No. fields of the sales and purchase documents. The obvious choice would be to display the data directly from the Sales Header and Purchase Header records. However, the requirement is to display all of them together chronologically, which means they will need to be stored in, and be read from, one common dataset.

      -

      Step 1: Design a new table that will hold the temporary dataset.

      +

      Step 1: Design a new table that will hold the temporary dataset.

      The UI must display the Document Type, Document No., Document Date, Amount Incl. VAT and the name of the salesperson or purchaser. The table is designed as follows.

      Since this table is used to sort the data by Document Date, a “Document Date” key is added to the table.

      @@ -325,9 +343,9 @@ ALGuidelines.Dev

      Purchase Header dataitem should be linked to the Contact dataitem through the DataItemLink property – “Buy-from Contact No.=FIELD(No.)”

    -

    Step 2: In the Sales Header – OnAfterGetRecord, write the following code to populate the data buffer:

    +

    Step 2: In the Sales Header – OnAfterGetRecord, write the following code to populate the data buffer:

    -

    Where:

    +

    Where:

    • ContactDocumentBuffer is a temporary global variable of the buffer table created.

      @@ -340,17 +358,18 @@ ALGuidelines.Dev

    Similar code must be written in Purchase Header – OnAfterGetRecord to store data from the purchase documents into the buffer.

    -

    Step 3: Iterate through the temporary records in the Integer dataitem.

    +

    Step 3: Iterate through the temporary records in the Integer dataitem.

    Since the requirement is to sort the data by Document Date, we must sort the ContactDocumentBuffer table by this key. Also, by the time the execution of Integer – OnPreDataItem begins, the number of records in the temporary buffer should be known already. Therefore, we can limit the number of times to repeat the Integer dataitem to the number of records in the buffer.

    We meet both requirements with the following lines of code in Integer - OnPreDataItem:

    -

    ContactDocumentBuffer.SETCURRENTKEY(“Document Date”);

    -

    SETRANGE(Number,1,ContactDocumentBuffer.COUNT);

    -

    Lastly, we must move the record pointer by 1 record every time we loop through the Integer dataitem. So, in Integer – OnAfterGetRecord, we add the following lines of code:

    -

    IF Number = 1 THEN

    -

    ContactDocumentBuffer.FINDFIRST

    -

    ELSE

    -

    ContactDocumentBuffer.NEXT;

    -

    Now, the only task that remains is to design the RDLC layout. (Not part of this application pattern description.)

    +
    ContactDocumentBuffer.SETCURRENTKEY("Document Date"); 
    +
    +SETRANGE(Number,1,ContactDocumentBuffer.COUNT);
    +

    Lastly, we must move the record pointer by 1 record every time we loop through the Integer dataitem. So, in Integer – OnAfterGetRecord, we add the following lines of code:

    +
    IF Number = 1 THEN
    +    ContactDocumentBuffer.FINDFIRST
    +ELSE
    +    ContactDocumentBuffer.NEXT;
    +

    Now, the only task that remains is to design the RDLC layout. (Not part of this application pattern description.)

    NAV Usages

    This is a commonly used pattern in several reports, such as:

      @@ -379,12 +398,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/Fig-11.jpg b/navpatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/Fig-11.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0bc23569017dd4a959998f9a3fb20bafcd186bcf GIT binary patch literal 54058 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 KAVK)+)c*j)p0iu8+MLHsI6-j7{ z2wWm6APGHG=}1r_O%!=}=e;-gd-J|O-pu>W?wLJ%&iS1=yJu!+562Ga0As*0An^D2 zPk>lJzrhLygIFM}5XkSz#?Hyk#>T-0fpBnhaBy-R0m2UD;pT$=UjHWf9sWD&XmGJX z*nWHb-*DIg;9~+zde3?gIV}k`6VGLYywh7w#ToXkXFT?Jbx?ul#HEMiJ%%Hl87!lV8h@gYiv8rv~Q-$q!CfCgnat zc~xijW@l7*V$v7ZaTIv3JcMvz#|nn7M!Ej*ATcmrys(R%!f!GS=eNFUF*=OCjmKQ| z-@{Uev?deF;CvmYCw!hvTAwP|k-XRy6 z!tP&HaU+Q4EF7*Y6LTQp|JmLyV>P&;A7Mn^OH{%e%kvT+YG^fF>%#oKd`2oT;mH{5l$3IL`R5p90 z`Pzh(!fF?qfxR-^O0YAhHlhr zHwMnuUAoyr$-oqOybl%A!+u%lewOv%XBh~vup+p06Yr_cT~%9FU-6{6rn<5`?N)>E zEv`~|QR2tRmcAZq?F}4$D;cfpt+~s>tl(s4F}O zYkzl%HK({>ezJ{VvyqT@2(UZ366TYkUH zk-x0ks!x2>nZag{jQ({MZfY~6+*)9lySaEPFF0^)XLcv@%&HY_OR*9IFDb`Z#XS&| z4Enf1kEg`7j?qoeh%IxtdM<0CQZw31;#KIgz|45wnA$xOxq*gs+;cRJp}cg244JWj zME*$EKd)*SVH5<>-VuG$p0m9HEuW?bhu2@=NITtUFRCD9EAS|pck070?4RIF6s40y z^%sG2!3k&dNOfq`>&*utF3O#jLb7G?vALhEv&okg=<2Z6I|MVcmHZV+%=N&+mJT3A z2~bQ-K>k-l{qu_Xc)gf{6x{=9y&Rtd%c|t0dpfzsE!`*8JF@h&q?dJ5#3p~9Fukm`Z~2v0z&!j$o6 z$GnO>1>xv7wP~cySDWvTv-&1X@^iZ7EemOLORyGXl?ME@U%BoXmOWiU)L=dJe(~*? z*CY;9+{BGvnEkeDz%ds&=u~KoudJ!BzX9Q11ale=e~Kz@`rKb#EaKGCED-+)_d7+=X1O&SC;b1N@^>UDM19z z+uW5;P+lEH8bsI8nAv-ox2so1a-L$a#5aN0lT}1-#79A3?L6h7GlUh%icQ_pYh(vU zoy`UvkBSZ&^3|JhZg|jSvo87#U=ioM*GP?EoOX=P@;C%=mbP%Z)AHq2YO`KMtm{HN z`>D|z^Bf;^;N{&Pdw=SoXKcz}C56tA0$kZ{7bP4)T^X)8n{!1e7TgiT;#V*cDwH9wRp1_DX;V~l6EgIzFn_!%(X zR0KCRp#|V+STQs)0h|1b5YrXE$gMLnk!S84Fy+Yvw+ajYDoFPl=jH5$`ai z$;lu+MifrFUP^7;)zOhhI!x3|%waG=C|A@I z$?>#tTvzhQ@_b+BfxC<3%0_PGD@sVF&BW!aP_9t&!;*E01U7w&g!oFU{3@aFX$=uw z8}U#!-)~57qXJdn6EQ{g6R%zmvWc_UGx8|e6!5Dit?u8sqg%&GlKUcc8kOG8nvxup zMOv(5=>{f5SMR-ab_oz_*Nt5>L*yASKS2qSP2Z4QkI2MXR-n=bQ1of+w+h!dTPlq< z;=1ZvRhjXX!WSec_({|Z@*|&TEUDBw1C0*6dvS z9HOVZa=7&-VbO9aRxixUrHfAL5ckr%ljR7idXJO|!WvFk%suGsS5;XzT6c1| z2}5{5E6dydLRDcqliqB7nPy7ja1l#QS`ZvWYoRW&Yb2BTm40+M0p3sf$7j`;4l@t%WQ8$;eo7&BNB%KF6&f=Qf&3MLo6y+x8Hxv#H_-H9|qYADV-`LvkjTBqalG_xin8UK~-=W-; zycj4=H<#=-JeQ}NX>-t&E@lb4-;=slSasEL$lg|`M&X@?`Zt=Ry*_&hl#$yUxgO`o zlf(Z7ny(+=Yy_`(VJb5nJ@I|obvL>Bm>IrjnHK_R&hE9FP^;L9g?Py9jQJ2OB{1N2 zXN!mgkAh~Rr9AP5u6ZY?lw#!B&`us(RNL)-Uhh=iT|?6)9PfRxQcC1_RN~MTpXh?e z+H_&db!`RM{-+t(8Q(CXhNf1NoEjy_rbAm$|2v0!db(I>&vDy9rvZ2!0;7nQXQ4n0 zei}TxNO*T9JyJ)b56U2Di6m3mW)t*{Omk$9JFRJSaYak}0epDFpl@G7k~S8=R=q7i zfsi0WlgUR}e429qzZOrMkTbW-f?Tb&F=rNSOY_m89_1bRDu)!2t*4ry?B zAPpx1$PXp?Rox+sZIw5Sh1iJ`_VuV>_CK+4!S~$@ooIMxot#nn)gcYd3rifi#E}$? z_Ohrhjqu&$WP8bY(1^tb@6;c^2<93p(iPmm60XF`e4VP2UrwT#|A`^Ze%Fk~ z&|OwxOwF%X`*7ZFaJW!Aszlvk z_i5;p?~)sZbx@JS5Y=e!Tg4&Hk(*OFjdqSVRWSShmKb7wCFYu_Xu+&!O{{#<0o?SQ zy%{~xFNxk=TKgrm7p;Ws$u}=`uFAo7qB~|aZ0nJ5m7Ep*@%SJ=l znr!my+bQmiVKv{lJRu)9QJF}wETaXcmbe1z%-pWkL_ke>RdPw))5=_;Ma;Lw2aDW1 zfIb#raAgeZknx55kg?fs@eQZR1Mi|VS}3U= zpTidk^s{+-OQT#Fsn)Lhm5A>_T0A{*mghiY^$J1ttNHKlxRkZKO|GrVB|~m>Xqa~z zd;9A(A+oc|f(t>k$nRdcu4u{XDvVXKznmh`0RBiMaVN-3HH%+rNR)?Dp z+UonMvJUZfjw6v{u{(=(5|QA@jfQ;erpU!@j+&>LwdVU|*tLL@o6dU`FYQ{2P`>J1 z%=dczq3O6AKa@^Kl~g^hOJ+ZtOSZHH)T9dM-jXq;gWmMBDa|KNIo3}LBBm%7w&Qc* zSZ=-coS|nrLeGtI?`Mrs*g!u(D=|M@t8ba zgCAm(a^yq6{VgnhkAi-mXSW^^_#t=gDT!OpTlqWFT;@%soHxK@lV}E9s9irWc|OB9 z+iZK~kAUj=+Yb6JODwhN3C05*5@JWco##(t5NHp#P-10+@IZg4*ve(6rEX+IF>QBj z^4IkHn+0DPm5ACVO*`5U>$DUzU_NYt?1Y8C*`1dc{d$9gM53uUEFU#W>&%1nXbW0+ z*~MWBBBRY%1YIUzv6_Fa%Vu?MR-hOIbvE&EqP;){`@g!>v}tiwPcp2euCTUeU7*Lz z%p5<^nOO7nb5&qKY0bmY)1T*1URbx!><^xCVdcph`_U0Un9m<|nPomQr;Zf|M>E2d z_4D)P9dwz}p&yz!&w$JqDpwenI&k9Ga5QoJ_s!+grh zfllf1tHz#iKPc6`zeuZbjd;(+bhrw~VAbfN87JVO50(qm%RvRjp7%X$X`>?L_b<)B ziH_YaFo6JJcTT3TF4KCHD{{`>|D5b<)MW*jOy222nI4O&ZPWU++sZo1<}zB(E4rCB z&2IiL<^A{BPm7<>^CS|=*2=PnW&6LWTF)0OEbRW^318ihc<|k^_)^Bkx4%>}gDbSB ei`VYHEoEK{6#m3`%yo~5XWmVkvLReO9RCk~ug+%x literal 0 HcmV?d00001 diff --git a/navpatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/index.html b/navpatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/index.html index ba58f6f2..02cf360d 100644 --- a/navpatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/index.html +++ b/navpatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/index.html @@ -5,16 +5,16 @@ Totals and Discounts on Subpages Sales and Purchases :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -364,15 +364,14 @@ ALGuidelines.Dev

        After Platform added support for this specific case, the layout is very simple, as follows.

        This will produce the layout we wanted:

        -

        +

        UI – Manual Totals Update

        As mentioned before, there are cases when the user is informed that totals/discounts might not be up to date and that it should select a link in order to get the updated values.

        The link is part of the sub-part where totals and discounts are added. The visibility is controlled by actually controlling the text (message, or empty), because Platform does not support making only one visible/invisible (it must be a group). When the link is displayed, the styling is also changed for the total/discount controls, and the values are set to 0.

        -

        +

        We have implemented these scenarios for sales and purchase documents (order, invoice, quote, credit memos, return orders, including posted documents). Note that the implementation in NAV 2015 is a bit different than the one in NAV 2014 C5 objects (1300 range). It was decided not to merge the two implementations, among other reasons because the NAV 2014 C5 implementation does not fit the full application.

        The pattern can easily be extended to new sales and purchase documents. If the pattern is followed, it can also be extended to other types of documents, such as in services, jobs, etc.

        -

        Note: This new pattern is not implemented in the North America and India versions. This is due to special local features in the two versions regions and the multitude of sales tax/vat options that would make the solution non-user friendly and not optimized for performance.

        Ideas for Improvement

        Platform support to be able to trigger full-page update from the lines. Support does exist today, but it is not programmatically controlled at design time. This means that it cannot be used in all cases.

        @@ -391,12 +390,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/transfer-custom-fields/index.html b/navpatterns/1-patterns/transfer-custom-fields/index.html index f8aba2c0..a7da1477 100644 --- a/navpatterns/1-patterns/transfer-custom-fields/index.html +++ b/navpatterns/1-patterns/transfer-custom-fields/index.html @@ -5,16 +5,16 @@ Transfer Custom Fields :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -269,12 +269,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/1-patterns/variant-facade/index.html b/navpatterns/1-patterns/variant-facade/index.html index 199e7f3b..8afb09af 100644 --- a/navpatterns/1-patterns/variant-facade/index.html +++ b/navpatterns/1-patterns/variant-facade/index.html @@ -5,16 +5,16 @@ Variant Facade :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + + @@ -262,11 +272,10 @@ ALGuidelines.Dev

      Variant Facade

      By Nikola Kukrika, waldo and Gary Winter

      -

      Abstract

      +

      Abstract

      The Variant façade provides a single interface that can take any Record, RecordRef or RecordID as an argument. With this pattern the code is encapsulated within the single object, with clear separation between common and table specific code.

      -
      -

      Problem

      +

      Problem

      Since NAV is strongly typed, developers often need to duplicate functionality in order to add support for a new table. The developer would typically start by duplicating the function, changing the record type and implement slight modifications to the code if needed.

      The problem with this approach is that code duplication is one of the worst things in software development – it makes code harder to understand, maintain, extend and test.

      One of the good examples of this approach and the resulting duplication is codeunit 229, Document-Print:

      @@ -285,15 +294,15 @@ ALGuidelines.Dev

      Testing the code is hard since the tests need to be replicated.

      Constant cost of adding support for new tables – when the new record type needs to be supported, it has a constant cost. Adding a support for a new record will increase the Maintenance tax and it will make the code harder to understand and extend.

      Conclusion - If the functionality needs to be used for many records the approach of duplicating the functions should be avoided.

      -

      Solution

      +

      Solution

      The Variant façade pattern provides a single interface that would not need to be changed in the future. It will be able to take any record as a parameter. Common code should be kept separately from record specific code and both must be very visible so the developers can easily see what the differences are.

      The Key components of the pattern are:

      -

      Signature

      +

      Signature

      Instead of hardcoding a record type a variant is used as an argument. A Variant Façade function can receive three types of data: Record, RecordID, or RecordRef. This way, it can be reused anywhere in the product and the code will still work.

      A good practice is to combine this pattern with the Argument Table pattern to make sure that the signature does not change (Additional parameters can be added to the argument table without impacting existing code.). If the Argument Table is not needed, it should not be placed in the signature - it is always possible to add it at a latter point and have two public functions (one with and one without arguments).

      Example - For codeunit 229, Document-Print all of the public methods can be simply replaced with a single public method like this:

      With this approach the façade function can serve all of the record types and will not need to change in the future.

      -

      Casting to a record ref

      +

      Casting to a record ref

      After the signature it is necessary to decide if the function will support passing of the Record ID and the RecordRef. Code tends to be easier to understand and maintain if only the records are passed as the arguments, however in some cases it is needed to support the other two types.

      • Support for Record, Record ID and RecordRef:
      • @@ -303,93 +312,98 @@ ALGuidelines.Dev
        • Support Records only - If the function supports only passing in the record, it is a good practice to check if the variant is a record:
        -

        IF NOT RecordVariant.ISRECORD THEN

        -

        ERROR(NotARecordErr);

        -

        Using the variant

        +
        IF NOT RecordVariant.ISRECORD THEN
        +    ERROR(NotARecordErr);
        +

        Using the variant

        The variant can be passed instead of record when calling the Page.RUN, Codeunit.RUN or Report.RUN statically:

        For example:

        -

        Page.RUN(PageID,Variant);

        -

        Codeunit.RUN(CodeunitID,Variant);

        -

        Report.RUN(ReportID,Variant);

        -

        These calls are identical to using an actual instance of the record, since the variant will be casted to the record automatically, with all filters, markings and values preserved.

        -
        +
        Page.RUN(PageID,Variant);
        +
        +Codeunit.RUN(CodeunitID,Variant);
        +
        +Report.RUN(ReportID,Variant);
        +

        These calls are identical to using an actual instance of the record, since the variant will be casted to the record automatically, with all filters, markings and values preserved.

        In case the variant was casted to the RecordRef (by using DataTypeManagement.GetRecordRef(RecRelatedVariant,RecordRef)), it is still possible to invoke the functions statically.

        The RecordRef simply needs to be casted into a variant and passed as a parameter, for example:

        -

        VariantArgument := RecordRef;

        -

        Page.RUN(PageID,VariantArgument);

        -

        Variant can always casted back to the original record in the table specific code, exact process is described below.

        -

        Table specific code

        +
        VariantArgument := RecordRef;
        +
        +Page.RUN(PageID,VariantArgument);
        +

        Variant can always casted back to the original record in the table specific code, exact process is described below.

        +

        Table specific code

        To do table-specific processing, it is necessary to get the RecordRef first, since NUMBER parameter will tell us which table it is. In the table-specific code, it is possible to cast the variant back to the original record type, so data is accessible and it is possible to invoke functions.

        To do this, the best practice is to use the COPY function to preserve filters:

        -

        SalesHeader.COPY(RecordVariant);

        -

        Assigning directly such as SalesHeader := RecordVariant, is possible, however all filters will be lost.

        +
        SalesHeader.COPY(RecordVariant);
        +

        Assigning directly such as SalesHeader := RecordVariant, is possible, however all filters will be lost.

        Example of table specific code:

        -

        CASE RecordRef.NUMBER OF

        -

        DATABASE::“Sales Header”:

        -

        BEGIN

        -

        SalesHeader.COPY(RecordVariant);

        -

        SalesHeader.PrintDocument;

        -

        END;

        -

        DATABASE::“Purchase Header”:

        -

        BEGIN

        -

        PurchaseHeader.COPY(RecordVariant)

        -

        -

        END;

        -

        ….

        -

        It is a good practice to try to avoid the table specific code if possible.

        +
        CASE RecordRef.NUMBER OF
        +    DATABASE::"Sales Header":
        +        BEGIN
        +            SalesHeader.COPY(RecordVariant);
        +            SalesHeader.PrintDocument;
        +        END;
        +    DATABASE::"Purchase Header":
        +        BEGIN
        +            PurchaseHeader.COPY(RecordVariant)
        +        ...
        +        END;
        +    ....
        +

        It is a good practice to try to avoid the table specific code if possible.

        One of the issues with table specific code is that the CASE statement can easily explode when large number of records are supported.

        There are two possible solutions:

        1. Move the calculations outside of the façade code unit and pass it in as part of the argument table.

        For example, instead of having a case like this within DocumentPrint codeunit:

        -

        CASE RecordRef.NUMBER OF

        -

        DATABASE::“Sales Header”:

        -

        BEGIN

        -

        SalesHeader.COPY(RecordVariant);

        -

        CASE SalesHeader.“Document Type” OF

        -

        SalesHeader.“Document Type”::Quote:

        -

        ReportSelections.SETRANGE(Usage,ReportSelections.Usage::“S.Quote”);

        -

        SalesHeader.“Document Type”::“Blanket Order”:

        -

        ReportSelections.SETRANGE(Usage,ReportSelections.Usage::“S.Blanket”);

        -

        SalesHeader.“Document Type”::Order:

        -

        ReportSelections.SETRANGE(Usage,ReportSelections.Usage::“S.Order”);

        -

        SalesHeader.“Document Type”::“Return Order”:

        -

        ReportSelections.SETRANGE(Usage,ReportSelections.Usage::“S.Return”);

        -

        SalesHeader.“Document Type”::Invoice:

        -

        ReportSelections.SETRANGE(Usage,ReportSelections.Usage::“S.Invoice”);

        -

        SalesHeader.“Document Type”::“Credit Memo”:

        -

        ReportSelections.SETRANGE(Usage,ReportSelections.Usage::“S.Cr.Memo”);

        -

        END;

        -

        If the ReportSelections.Usage is simply passed into the function from outside, then the case statement is not needed at all. For example:

        -

        PrintDocumentArguments.“Report Selection Usage” := SalesHeader.GetReportSelectionUsage;

        -

        DocumentPrint.PrintDocument(SalesHeader,PrintDocumentArguments);

        -

        Where GetReportSelection usage is coded like this:

        -

        CASE SalesHeader.“Document Type” OF

        -

        SalesHeader.“Document Type”::Quote:

        -

        EXIT (ReportSelections.Usage::“S.Quote”);

        -

        SalesHeader.“Document Type”::“Blanket Order”:

        -

        EXIT(ReportSelections.Usage::“S.Blanket”);

        -

        SalesHeader.“Document Type”::Order:

        -

        EXIT(ReportSelections.Usage::“S.Order”);

        -

        SalesHeader.“Document Type”::“Return Order”:

        -

        EXIT(ReportSelections.Usage::“S.Return”);

        -

        SalesHeader.“Document Type”::Invoice:

        -

        EXIT(ReportSelections.Usage::“S.Invoice”);

        -

        SalesHeader.“Document Type”::“Credit Memo”:

        -

        EXIT(ReportSelections.Usage::“S.Cr.Memo”);

        -

        This way the code is much more reusable and simpler to read.

        +
        CASE RecordRef.NUMBER OF
        +    DATABASE::"Sales Header":
        +        BEGIN
        +            SalesHeader.COPY(RecordVariant);
        +            CASE SalesHeader."Document Type" OF
        +                SalesHeader."Document Type"::Quote:
        +                    ReportSelections.SETRANGE(Usage,ReportSelections.Usage::"S.Quote");
        +                SalesHeader."Document Type"::"Blanket Order":
        +                    ReportSelections.SETRANGE(Usage,ReportSelections.Usage::"S.Blanket");
        +                SalesHeader."Document Type"::Order:
        +                    ReportSelections.SETRANGE(Usage,ReportSelections.Usage::"S.Order");
        +                SalesHeader."Document Type"::"Return Order":
        +                    ReportSelections.SETRANGE(Usage,ReportSelections.Usage::"S.Return");
        +                SalesHeader."Document Type"::Invoice:
        +                    ReportSelections.SETRANGE(Usage,ReportSelections.Usage::"S.Invoice");
        +                SalesHeader."Document Type"::"Credit Memo":
        +                    ReportSelections.SETRANGE(Usage,ReportSelections.Usage::"S.Cr.Memo");
        +        END;
        +

        If the ReportSelections.Usage is simply passed into the function from outside, then the case statement is not needed at all. For example:

        +
        PrintDocumentArguments."Report Selection Usage" := SalesHeader.GetReportSelectionUsage;
        +
        +DocumentPrint.PrintDocument(SalesHeader,PrintDocumentArguments);
        +

        Where GetReportSelection usage is coded like this:

        +
        CASE SalesHeader."Document Type" OF
        +    SalesHeader."Document Type"::Quote:
        +        EXIT (ReportSelections.Usage::"S.Quote");
        +    SalesHeader."Document Type"::"Blanket Order":
        +        EXIT(ReportSelections.Usage::"S.Blanket");
        +    SalesHeader."Document Type"::Order:
        +        EXIT(ReportSelections.Usage::"S.Order");
        +    SalesHeader."Document Type"::"Return Order":
        +        EXIT(ReportSelections.Usage::"S.Return");
        +    SalesHeader."Document Type"::Invoice:
        +        EXIT(ReportSelections.Usage::"S.Invoice");
        +    SalesHeader."Document Type"::"Credit Memo":
        +        EXIT(ReportSelections.Usage::"S.Cr.Memo");
        +

        This way the code is much more reusable and simpler to read.

        1. Use Rules Table to replace the code with data-driven approach.

        Setup table would contain the list of the reports and their usages. Based on Table ID and usage it is possible to set filters on the setup table and run the object ID from the result.

        For example:

        -

        ReportSelectionSetup.SETRANGE(“Table ID”, RecordRef.NUMBER);

        -

        ReportSelectionSetup.SETRANGE(“Usage Type”, RecordRef.FieldValue(ArgumentTable.“Usage Type”);

        -

        ReportSelectionSetup.FINDFIRST;

        -

        REPORT.RUN(ReportSelectionSetup.“Report ID”,VariantRecord);

        -
        -

        Example

        +
        ReportSelectionSetup.SETRANGE("Table ID", RecordRef.NUMBER);
        +
        +ReportSelectionSetup.SETRANGE("Usage Type", RecordRef.FieldValue(ArgumentTable."Usage Type");
        +
        +ReportSelectionSetup.FINDFIRST;
        +
        +REPORT.RUN(ReportSelectionSetup."Report ID",VariantRecord);
        +

        Example

        The following code illustrates how the Variant Façade pattern can be used to implement the Document-Print Codeunit.

        @@ -397,7 +411,7 @@ ALGuidelines.Dev

        Code duplication is avoided, specific code is isolated in the PrepareRecord function, there is clear extension point to add support for new records in the future. Since code is not duplicated and there is a single flow through the method, inserting hooks and events in the future will be straightforward.

        When adding the support for new records in most cases it is not need to change any code within the method, thus the cost of extending the usage is minimal.

        Note - PrepareRecord function is placed for the illustrational purposes. An improvement would be to move all of the code from the PrepareRecord function before calling the function. So for the SalesHeader and PurchaseHeaders discounts should be calculated before invoking the function. For passing of the argument it should be one of the fields in the Argument Table, thus the entire specific code would be eliminated.

        -

        Consequences

        +

        Consequences

        • Not needed if the functionality needs to support few tables. Don’t use it as a hammer
        • Strongly typing the records has it benefits since it is easier to find usages, errors will be visible at the compilation time.
        • @@ -405,7 +419,7 @@ ALGuidelines.Dev
        • Be careful with filters and marks, if the function needs to support multiple records. Test these cases thoughtfully because with bad placement of code the filters can easily be lost.
        • Case statements can explode if there are too many tables that require specific processing. Then it is a must to find a way to keep the number of options in the CASE statement low. The Rules Table pattern and adding specifics to the Argument Table before invoking the code could help with keep the list shorter.
        -

        NAV usages

        +
        • Codeunit 452 - Report Distribution Management
        • Codeunit 700 - Page Management
        • @@ -417,12 +431,10 @@ ALGuidelines.Dev
        • Codeunit 1531 - Workflow Change Rec Mgt.
        • Codeunit 1535 - Approvals Mgmt.
        -

        Related Topics

        +

        OO Facade https://en.wikipedia.org/wiki/Facade_pattern

        Argument Table pattern - https://community.dynamics.com/nav/w/designpatterns/245.argument-table-pattern

        Rules Table pattern

        -

        < –[if supportAnnotations]–> -< –[endif]–>

      @@ -435,12 +447,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/2-anti-patterns/index.html b/navpatterns/2-anti-patterns/index.html index e79c1f19..c6e4d45f 100644 --- a/navpatterns/2-anti-patterns/index.html +++ b/navpatterns/2-anti-patterns/index.html @@ -6,16 +6,16 @@ 2. Anti-Patterns :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev - - - + + +
        @@ -266,12 +266,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/2-anti-patterns/nav-upgrade/index.html b/navpatterns/2-anti-patterns/nav-upgrade/index.html index be37fb43..248cd414 100644 --- a/navpatterns/2-anti-patterns/nav-upgrade/index.html +++ b/navpatterns/2-anti-patterns/nav-upgrade/index.html @@ -5,16 +5,16 @@ Nav Upgrade :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -273,7 +273,6 @@ ALGuidelines.Dev

        Problem: assuming that the upgrade table contains data.

        If the table is empty, it means that either the upgrade has run, or there was no data in the original tenant; in both cases, the upgrade code should exit immediately.

        Solution: if using an upgrade table, always validate that the table contains data before doing anything.

        -

        Upgrade code, can it be rerun safely?

        Problem: if the upgrade code is not written in a way that makes it runnable twice (idempotent), then several failures can happen, including something as critical as data corruption.

        In on premise NAV installations, if something fails at upgrade, there is no way to run only the “remaining” tasks. You will need to run the whole upgrade again, and might end-up with data that you cannot trust.

        @@ -317,12 +316,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/2-anti-patterns/reusable-bugs/index.html b/navpatterns/2-anti-patterns/reusable-bugs/index.html index 1b17f65b..ab52d77c 100644 --- a/navpatterns/2-anti-patterns/reusable-bugs/index.html +++ b/navpatterns/2-anti-patterns/reusable-bugs/index.html @@ -5,16 +5,16 @@ Reusable Bugs :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -316,12 +316,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/by-reference-parameters/index.html b/navpatterns/3-cal-coding-guidelines/design/by-reference-parameters/index.html index 071f072f..d4da96ad 100644 --- a/navpatterns/3-cal-coding-guidelines/design/by-reference-parameters/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/by-reference-parameters/index.html @@ -5,16 +5,16 @@ By Reference Parameters :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -258,19 +258,19 @@ ALGuidelines.Dev

        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\]);
        +
        LOCAL PROCEDURE ShowMessage@15(VAR Text@1000 : Text[250]);
         BEGIN
        -Text := GetMessageText;
        -IF (Text <\> '') AND GenJnlLineInserted THEN
        -MESSAGE(Text);
        +    Text := GetMessageText;
        +    IF (Text <> '') AND GenJnlLineInserted THEN
        +        MESSAGE(Text);
         END;
         

        Good code

        -
        LOCAL PROCEDURE ShowMessage@15(Text@1000 : Text\[250\]);
        +
        LOCAL PROCEDURE ShowMessage@15(Text@1000 : Text[250]);
         BEGIN
        -Text := GetMessageText;
        -IF (Text <\> '') AND GenJnlLineInserted THEN
        -MESSAGE(Text);
        +    Text := GetMessageText;
        +    IF (Text <> '') AND GenJnlLineInserted THEN
        +        MESSAGE(Text);
         END;
         
        @@ -285,12 +285,12 @@ END;
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/class-coupling/index.html b/navpatterns/3-cal-coding-guidelines/design/class-coupling/index.html index a7d9d8a5..7f53f184 100644 --- a/navpatterns/3-cal-coding-guidelines/design/class-coupling/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/class-coupling/index.html @@ -5,16 +5,16 @@ Class Coupling :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -257,7 +257,7 @@ ALGuidelines.Dev

        Class Coupling

        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  
        +
        Any procedure / trigger that has class coupling of > 30  
         

        Good code

        Any procedure / trigger that has class coupling of <= 30\.
        @@ -277,12 +277,12 @@ Class coupling is computed by summing the unique instances of the following in a
         
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/cyclomatic-complexity/index.html b/navpatterns/3-cal-coding-guidelines/design/cyclomatic-complexity/index.html index e3cbd91a..d7f8d5dd 100644 --- a/navpatterns/3-cal-coding-guidelines/design/cyclomatic-complexity/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/cyclomatic-complexity/index.html @@ -5,16 +5,16 @@ Cyclomatic Complexity :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -257,7 +257,7 @@ ALGuidelines.Dev

        Cyclomatic Complexity

        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].  
        +
        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.
        @@ -277,12 +277,12 @@ The CC3 version is computed by summing the following in a code block:
         
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/encapsulate-local-functionality/index.html b/navpatterns/3-cal-coding-guidelines/design/encapsulate-local-functionality/index.html index fc16c229..6ca15411 100644 --- a/navpatterns/3-cal-coding-guidelines/design/encapsulate-local-functionality/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/encapsulate-local-functionality/index.html @@ -5,16 +5,16 @@ Encapsulate Local Functionality :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -268,12 +268,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/findset-findfirst-findlast/index.html b/navpatterns/3-cal-coding-guidelines/design/findset-findfirst-findlast/index.html index 84cb4b0f..4383185f 100644 --- a/navpatterns/3-cal-coding-guidelines/design/findset-findfirst-findlast/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/findset-findfirst-findlast/index.html @@ -5,16 +5,16 @@ FINDSET FINDFIRST FINDLAST :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -258,23 +258,23 @@ ALGuidelines.Dev

        FINDSET, FIND('+') or FIND('-') should only be used when NEXT is used and vice versa.

        Bad code

        IF Cust.FIND('-') THEN
        -ERROR(CustIsBlockErr)
        +    ERROR(CustIsBlockErr)
         

        Good code

        IF Cust.FINDFIRST THEN
        -ERROR(CustIsBlockErr)
        +    ERROR(CustIsBlockErr)
         

        Bad code

        IF Cust.FINDFIRST THEN
        -REPEAT
        -...
        -UNTIL Cust.NEXT = 0;
        +    REPEAT
        +        ...
        +    UNTIL Cust.NEXT = 0;
         

        Good code

        IF Cust.FINDSET THEN
        -REPEAT
        -...
        -UNTIL Cust.NEXT = 0;
        +    REPEAT
        +        ...
        +    UNTIL Cust.NEXT = 0;
         
        @@ -288,12 +288,12 @@ UNTIL Cust.NEXT = 0;
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/index.html b/navpatterns/3-cal-coding-guidelines/design/index.html index 278604b9..4d08e87f 100644 --- a/navpatterns/3-cal-coding-guidelines/design/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/index.html @@ -6,16 +6,16 @@ Design :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -275,12 +275,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/index.xml b/navpatterns/3-cal-coding-guidelines/design/index.xml index ed325675..b590ab6b 100644 --- a/navpatterns/3-cal-coding-guidelines/design/index.xml +++ b/navpatterns/3-cal-coding-guidelines/design/index.xml @@ -1,13 +1,13 @@ Design on AL Guidelineshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/Recent content in Design on AL GuidelinesHugo -- gohugo.ioen-usBy Reference Parametershttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/by-reference-parameters/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/by-reference-parameters/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 &lt;\&gt; '') AND GenJnlLineInserted THEN MESSAGE(Text); END; Good code -LOCAL PROCEDURE ShowMessage@15(Text@1000 : Text\[250\]); BEGIN Text := GetMessageText; IF (Text &lt;\&gt; '') AND GenJnlLineInserted THEN MESSAGE(Text); END;Class Couplinghttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/class-coupling/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/class-coupling/Do not write functions that have high class coupling. This makes the code hard to maintain. +LOCAL PROCEDURE ShowMessage@15(VAR Text@1000 : Text[250]); BEGIN Text := GetMessageText; IF (Text &lt;&gt; '') AND GenJnlLineInserted THEN MESSAGE(Text); END; Good code +LOCAL PROCEDURE ShowMessage@15(Text@1000 : Text[250]); BEGIN Text := GetMessageText; IF (Text &lt;&gt; '') AND GenJnlLineInserted THEN MESSAGE(Text); END;Class Couplinghttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/class-coupling/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/class-coupling/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 \&gt; 30 Good code +Any procedure / trigger that has class coupling of &gt; 30 Good code Any procedure / trigger that has class coupling of &lt;= 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\.Cyclomatic Complexityhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/cyclomatic-complexity/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/cyclomatic-complexity/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 \&gt; 25, using the CC3 version mentioned in [this article][anchor0]. Good code +Any procedure / trigger that has a cyclomatic complexity &gt; 25, using the CC3 version mentioned in [this article][anchor0]. Good code Any procedure / trigger that has a cyclomatic complexity &lt;= 25, using the CC3 version. The CC3 version is computed by summing the following in a code block: - each IF statement as 1\.Encapsulate Local Functionalityhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/encapsulate-local-functionality/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/encapsulate-local-functionality/Any function used local must be defined as local.FINDSET FINDFIRST FINDLASThttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/findset-findfirst-findlast/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/findset-findfirst-findlast/FINDSET, FIND('+') or FIND('-') should only be used when NEXT is used and vice versa. Bad code IF Cust.FIND('-') THEN ERROR(CustIsBlockErr) Good code @@ -19,7 +19,7 @@ PROCEDURE SetPurchLine@22(VAR CurrentPurchLine@1000 : Record 39); VAR Pegging@10 PROCEDURE SetPurchLine@22(VAR CurrentPurchLine@1000 : Record 39); VAR Pegging@1001 : Boolean; BEGIN Pegging := IsPegging(CurrentPurchLine); IF Pegging THEN CurrQuantity := CurrentPurchLine.&quot;Quantity (Base)&quot; ELSE CurrQuantity := CurrentPurchLine.&quot;Outstanding Qty. (Base)&quot;; END; Bad codeMaintainability Indexhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/maintainability-index/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/maintainability-index/Maintainability Index: Do not write functions that have a very low maintainability index. This makes the code hard to maintain. Bad code Any procedure / trigger that has a maintainability index &lt; 20 Good code -Any procedure / trigger that has a maintainability index \&gt;= 20\. The Maintainability Index is computed as a function: - Lines Of Code (inverse proportional) - the Halstead Volume - Cyclomatic Complexity (inverse proportional). More infoParameter Placeholdershttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/parameter-placeholders/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/parameter-placeholders/The number of parameters passed to a string must match the placeholders. +Any procedure / trigger that has a maintainability index &gt;= 20\. The Maintainability Index is computed as a function: - Lines Of Code (inverse proportional) - the Halstead Volume - Cyclomatic Complexity (inverse proportional). More infoParameter Placeholdershttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/parameter-placeholders/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/parameter-placeholders/The number of parameters passed to a string must match the placeholders. Bad code CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.'; ... ERROR(CannotDeleteLineErr,TABLECAPTION); Good code CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.'; ... ERROR(CannotDeleteLineErr); Bad code @@ -29,22 +29,22 @@ LOCAL PROCEDURE Code@1(); VAR CAJnlPostBatch@1001 : Codeunit 1103; BEGIN CAJnlPo LOCAL PROCEDURE Code@1(); BEGIN CODEUNIT.RUN(CODEUNIT::&quot;CA Jnl.-Post Batch&quot;,CostJnlLine); END;Unreachable Codehttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/unreachable-code/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/unreachable-code/Do not write code that will never be hit. It affects code readability and can lead to wrong assumptions. Bad code -IF Type &lt;\&gt; Type::FIELD THEN BEGIN ... ERROR(...); RecRef.CLOSE; END; Good code -IF Type &lt;\&gt; Type::FIELD THEN BEGIN ... RecRef.CLOSE; ERROR(...); END;Unused Initialized Variableshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/unused-initialized-variables/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/unused-initialized-variables/The value assigned to a variable must be used. Else the variable is not necessary. +IF Type &lt;&gt; Type::FIELD THEN BEGIN ... ERROR(...); RecRef.CLOSE; END; Good code +IF Type &lt;&gt; Type::FIELD THEN BEGIN ... RecRef.CLOSE; ERROR(...); END;Unused Initialized Variableshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/unused-initialized-variables/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/unused-initialized-variables/The value assigned to a variable must be used. Else the variable is not necessary. Bad code -PROCEDURE AddEntities@1(FilterStr@1000 : Text\[250\]); VAR Vendor@1001 : Record 23; Count@1002 : Integer; BEGIN Count := 0; Vendor.SETFILTER(&quot;No.&quot;,FilterStr); IF Vendor.FINDSET THEN REPEAT &quot;User ID&quot; := USERID; &quot;Vendor No.&quot; := Vendor.&quot;No.&quot;; IF INSERT THEN Count += 1; UNTIL Vendor.NEXT = 0; END; Good code -PROCEDURE AddEntities@1(FilterStr@1000 : Text\[250\]); VAR Vendor@1001 : Record 23; BEGIN Vendor.Unused Variableshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/unused-variables/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/unused-variables/Do not declare variables that are unused. +PROCEDURE AddEntities@1(FilterStr@1000 : Text[250]); VAR Vendor@1001 : Record 23; Count@1002 : Integer; BEGIN Count := 0; Vendor.SETFILTER(&quot;No.&quot;,FilterStr); IF Vendor.FINDSET THEN REPEAT &quot;User ID&quot; := USERID; &quot;Vendor No.&quot; := Vendor.&quot;No.&quot;; IF INSERT THEN Count += 1; UNTIL Vendor.NEXT = 0; END; Good code +PROCEDURE AddEntities@1(FilterStr@1000 : Text[250]); VAR Vendor@1001 : Record 23; BEGIN Vendor.Unused Variableshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/unused-variables/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/unused-variables/Do not declare variables that are unused. Unused variables affect readability. Bad code -PROCEDURE CheckPostingDate@23(CaptionEntryNo@1005 : Text\[50\]); BEGIN IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN ERROR(DateNotAllowedErr,Caption,EntryNo) IF PostingDate \&gt; MaxPostingDate THEN MaxPostingDate := PostingDate; END Good code -PROCEDURE CheckPostingDate@23(); BEGIN IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN ERROR(DateNotAllowedErr,Caption,EntryNo); IF PostingDate \&gt; MaxPostingDate THEN MaxPostingDate := PostingDate; END; Bad code +PROCEDURE CheckPostingDate@23(CaptionEntryNo@1005 : Text[50]); BEGIN IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN ERROR(DateNotAllowedErr,Caption,EntryNo) IF PostingDate &gt; MaxPostingDate THEN MaxPostingDate := PostingDate; END Good code +PROCEDURE CheckPostingDate@23(); BEGIN IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN ERROR(DateNotAllowedErr,Caption,EntryNo); IF PostingDate &gt; MaxPostingDate THEN MaxPostingDate := PostingDate; END; Bad code PROCEDURE IsReturned@14(EntryNo@1002 : Integer) : Decimal; VAR ItemEntry@1000 : Record 32; Quantity@1003 : Integer; BEGIN EXIT(-OutboundApplied(EntryNo,TRUE) - InboundApplied(EntryNo,TRUE)); END; Good codeVariable Capacity Mismatchhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/variable-capacity-mismatch/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/variable-capacity-mismatch/Do not assign a value to a variable whose capacity is smaller. It will throw an error at runtime. Bad code -FileName@1010 : Text\[250\]; ... UploadedFileName@1016 : Text\[1024\]; ... FileName := UploadedFileName; Good code -FileName@1010 : Text\[1024\]; ... UploadedFileName@1016 : Text\[1024\]; ... FileName := UploadedFileName; Bad code -FileName@1010 : Text\[250\]; ... UploadedFileName@1016 : Text\[1024\]; ... FileName := UploadedFileName; Good code -FileName@1010 : Text\[250\]; ... UploadedFileName@1016 : Text\[1024\]; .WITH Scope Name Collisionhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/with-scope-name-collision/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/with-scope-name-collision/Do not use the WITH scope when it has a variable whose name is the same as a local variable. This can lead to wrong code assumptions. +FileName@1010 : Text[250]; ... UploadedFileName@1016 : Text[1024]; ... FileName := UploadedFileName; Good code +FileName@1010 : Text[1024]; ... UploadedFileName@1016 : Text[1024]; ... FileName := UploadedFileName; Bad code +FileName@1010 : Text[250]; ... UploadedFileName@1016 : Text[1024]; ... FileName := UploadedFileName; Good code +FileName@1010 : Text[250]; ... UploadedFileName@1016 : Text[1024]; .WITH Scope Name Collisionhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/with-scope-name-collision/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/design/with-scope-name-collision/Do not use the WITH scope when it has a variable whose name is the same as a local variable. This can lead to wrong code assumptions. Given that &ldquo;Contract Type&rdquo; is a field on table ServiceContractHeader, then in the following example there is a parameter name clash with the field name. Which one will be used? Bad code PROCEDURE InsertData@1(&quot;Contract Type&quot;@1000 : Option...); ... BEGIN ... WITH ServiceContractHeader DO BEGIN . \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/initialized-variables/index.html b/navpatterns/3-cal-coding-guidelines/design/initialized-variables/index.html index a6c661da..f28b9d15 100644 --- a/navpatterns/3-cal-coding-guidelines/design/initialized-variables/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/initialized-variables/index.html @@ -5,16 +5,16 @@ Initialized Variables :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -259,41 +259,41 @@ ALGuidelines.Dev

        Bad code

        PROCEDURE SetPurchLine@22(VAR CurrentPurchLine@1000 : Record 39);
         VAR
        -Pegging@1001 : Boolean;
        +    Pegging@1001 : Boolean;
         BEGIN
        -IF Pegging THEN
        -CurrQuantity := CurrentPurchLine."Quantity (Base)"
        -ELSE
        -CurrQuantity := CurrentPurchLine."Outstanding Qty. (Base)";
        +    IF Pegging THEN
        +        CurrQuantity := CurrentPurchLine."Quantity (Base)"
        +    ELSE
        +        CurrQuantity := CurrentPurchLine."Outstanding Qty. (Base)";
         END;
         

        Good code

        PROCEDURE SetPurchLine@22(VAR CurrentPurchLine@1000 : Record 39);
         VAR
        -Pegging@1001 : Boolean;
        +    Pegging@1001 : Boolean;
         BEGIN
        -Pegging := IsPegging(CurrentPurchLine);
        -IF Pegging THEN
        -CurrQuantity := CurrentPurchLine."Quantity (Base)"
        -ELSE
        -CurrQuantity := CurrentPurchLine."Outstanding Qty. (Base)";
        +    Pegging := IsPegging(CurrentPurchLine);
        +    IF Pegging THEN
        +        CurrQuantity := CurrentPurchLine."Quantity (Base)"
        +    ELSE
        +        CurrQuantity := CurrentPurchLine."Outstanding Qty. (Base)";
         END;
         

        Bad code

        // In the example below, the function will always return FALSE.
         PROCEDURE GetItemsToPlan@22() : Boolean;
         BEGIN
        -SETRANGE("Document Type","Document Type"::Order);
        -...
        -FINDSET
        +    SETRANGE("Document Type","Document Type"::Order);
        +        ...
        +    FINDSET
         END;
         

        Good code

        PROCEDURE GetItemsToPlan@22() : Boolean;
         BEGIN
        -SETRANGE("Document Type","Document Type"::Order);
        -...
        -EXIT(FINDSET)
        +    SETRANGE("Document Type","Document Type"::Order);
        +        ...
        +    EXIT(FINDSET)
         END;
         
        @@ -308,12 +308,12 @@ END;
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/maintainability-index/index.html b/navpatterns/3-cal-coding-guidelines/design/maintainability-index/index.html index 25cceaf6..b4ceb4e1 100644 --- a/navpatterns/3-cal-coding-guidelines/design/maintainability-index/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/maintainability-index/index.html @@ -5,16 +5,16 @@ Maintainability Index :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -260,11 +260,11 @@ ALGuidelines.Dev
        Any procedure / trigger that has a maintainability index < 20  
         

        Good code

        -
        Any procedure / trigger that has a maintainability index \>= 20\.
        +
        Any procedure / trigger that has a maintainability index >= 20\.
         The Maintainability Index is computed as a function:
        -- Lines Of Code (inverse proportional)
        -- the Halstead Volume
        -- Cyclomatic Complexity (inverse proportional).
        +    - Lines Of Code (inverse proportional)
        +    - the Halstead Volume
        +    - Cyclomatic Complexity (inverse proportional).
         

        More info

        Bad code

        -
        Any procedure / trigger that is \> 100 lines of code  
        +
        Any procedure / trigger that is > 100 lines of code  
         

        Good code

        Any procedure / trigger that is <= 100 lines of code.
        @@ -290,12 +290,12 @@ A full C/AL Statement counts as 1 line of code
         
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/parameter-placeholders/index.html b/navpatterns/3-cal-coding-guidelines/design/parameter-placeholders/index.html index 19a1947d..882fb69e 100644 --- a/navpatterns/3-cal-coding-guidelines/design/parameter-placeholders/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/parameter-placeholders/index.html @@ -5,16 +5,16 @@ Parameter Placeholders :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -252,7 +252,6 @@ ALGuidelines.Dev
        • -
      @@ -265,7 +264,6 @@ ALGuidelines.Dev

      Parameter Placeholders

      The number of parameters passed to a string must match the placeholders.

      -

      Bad code

      CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
       ...
      @@ -276,7 +274,6 @@ ERROR(CannotDeleteLineErr,TABLECAPTION);
       ...
       ERROR(CannotDeleteLineErr);
       
      -

      Bad code

      CannotUseThisFieldErr@1020 : TextConst 'ENU=You cannot use this field for %2 fields.';
       ...
      @@ -287,7 +284,7 @@ ERROR(CannotUseThisFieldErr,0,Field.Class);
       ...
       ERROR(CannotUseThisFieldErr,Field.Class);
       
      -

      +

      @@ -300,12 +297,12 @@ ERROR(CannotUseThisFieldErr,Field.Class);
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/static-object-invocation/index.html b/navpatterns/3-cal-coding-guidelines/design/static-object-invocation/index.html index d2aff0f1..d77c3dfe 100644 --- a/navpatterns/3-cal-coding-guidelines/design/static-object-invocation/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/static-object-invocation/index.html @@ -5,16 +5,16 @@ Static Object Invocation :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -259,15 +259,15 @@ ALGuidelines.Dev

        Bad code

        LOCAL PROCEDURE Code@1();
         VAR
        -CAJnlPostBatch@1001 : Codeunit 1103;
        +    CAJnlPostBatch@1001 : Codeunit 1103;
         BEGIN
        -CAJnlPostBatch.Run(CostJnlLine);
        +    CAJnlPostBatch.Run(CostJnlLine);
         END;
         

        Good code

        LOCAL PROCEDURE Code@1();
         BEGIN
        -CODEUNIT.RUN(CODEUNIT::"CA Jnl.-Post Batch",CostJnlLine);
        +    CODEUNIT.RUN(CODEUNIT::"CA Jnl.-Post Batch",CostJnlLine);
         END;
         
        @@ -282,12 +282,12 @@ END;
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/unreachable-code/index.html b/navpatterns/3-cal-coding-guidelines/design/unreachable-code/index.html index 97821e2e..2b7a1cfc 100644 --- a/navpatterns/3-cal-coding-guidelines/design/unreachable-code/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/unreachable-code/index.html @@ -5,16 +5,16 @@ Unreachable Code :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -258,17 +258,17 @@ ALGuidelines.Dev

        Do not write code that will never be hit.

        It affects code readability and can lead to wrong assumptions.

        Bad code

        -
        IF Type <\> Type::FIELD THEN BEGIN
        -...
        -ERROR(...);
        -RecRef.CLOSE;
        +
        IF Type <> Type::FIELD THEN BEGIN
        +    ...
        +    ERROR(...);
        +    RecRef.CLOSE;
         END;  
         

        Good code

        -
        IF Type <\> Type::FIELD THEN BEGIN
        -...
        -RecRef.CLOSE;
        -ERROR(...);
        +
        IF Type <> Type::FIELD THEN BEGIN
        +    ...
        +    RecRef.CLOSE;
        +    ERROR(...);
         END;
         
        @@ -283,12 +283,12 @@ END;
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/unused-initialized-variables/index.html b/navpatterns/3-cal-coding-guidelines/design/unused-initialized-variables/index.html index 8b9fef3d..c11c2133 100644 --- a/navpatterns/3-cal-coding-guidelines/design/unused-initialized-variables/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/unused-initialized-variables/index.html @@ -5,16 +5,16 @@ Unused Initialized Variables :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -257,34 +257,34 @@ ALGuidelines.Dev

        Unused Initialized Variables

        The value assigned to a variable must be used. Else the variable is not necessary.

        Bad code

        -
        PROCEDURE AddEntities@1(FilterStr@1000 : Text\[250\]);
        +
        PROCEDURE AddEntities@1(FilterStr@1000 : Text[250]);
         VAR
        -Vendor@1001 : Record 23;
        -Count@1002 : Integer;
        +    Vendor@1001 : Record 23;
        +    Count@1002 : Integer;
         BEGIN
        -Count := 0;
        -Vendor.SETFILTER("No.",FilterStr);
        -IF Vendor.FINDSET THEN
        -REPEAT
        -"User ID" := USERID;
        -"Vendor No." := Vendor."No.";
        -IF INSERT THEN
        -Count += 1;
        -UNTIL Vendor.NEXT = 0;
        +    Count := 0;
        +    Vendor.SETFILTER("No.",FilterStr);
        +    IF Vendor.FINDSET THEN
        +        REPEAT
        +            "User ID" := USERID;
        +            "Vendor No." := Vendor."No.";
        +            IF INSERT THEN
        +                Count += 1;
        +        UNTIL Vendor.NEXT = 0;
         END;
         

        Good code

        -
        PROCEDURE AddEntities@1(FilterStr@1000 : Text\[250\]);
        +
        PROCEDURE AddEntities@1(FilterStr@1000 : Text[250]);
         VAR
        -Vendor@1001 : Record 23;
        +    Vendor@1001 : Record 23;
         BEGIN
        -Vendor.SETFILTER("No.",FilterStr);
        -IF Vendor.FINDSET THEN
        -REPEAT
        -"User ID" := USERID;
        -"Vendor No." := Vendor."No.";
        -IF INSERT THEN;
        -UNTIL Vendor.NEXT = 0;
        +    Vendor.SETFILTER("No.",FilterStr);
        +    IF Vendor.FINDSET THEN
        +        REPEAT
        +            "User ID" := USERID;
        +            "Vendor No." := Vendor."No.";
        +            IF INSERT THEN;
        +        UNTIL Vendor.NEXT = 0;
         END;
         
        @@ -299,12 +299,12 @@ END;
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/unused-variables/index.html b/navpatterns/3-cal-coding-guidelines/design/unused-variables/index.html index cb4a50c2..3010546f 100644 --- a/navpatterns/3-cal-coding-guidelines/design/unused-variables/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/unused-variables/index.html @@ -5,16 +5,16 @@ Unused Variables :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -258,36 +258,36 @@ ALGuidelines.Dev

        Do not declare variables that are unused.

        Unused variables affect readability.

        Bad code

        -
        PROCEDURE CheckPostingDate@23(CaptionEntryNo@1005 : Text\[50\]);
        +
        PROCEDURE CheckPostingDate@23(CaptionEntryNo@1005 : Text[50]);
         BEGIN
        -IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN
        -ERROR(DateNotAllowedErr,Caption,EntryNo)
        -IF PostingDate \> MaxPostingDate THEN
        -MaxPostingDate := PostingDate;
        +    IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN
        +        ERROR(DateNotAllowedErr,Caption,EntryNo)
        +    IF PostingDate > MaxPostingDate THEN
        +        MaxPostingDate := PostingDate;
         END
         

        Good code

        PROCEDURE CheckPostingDate@23();
         BEGIN
        -IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN
        -ERROR(DateNotAllowedErr,Caption,EntryNo);
        -IF PostingDate \> MaxPostingDate THEN
        -MaxPostingDate := PostingDate;
        +    IF GenJnlCheckLine.DateNotAllowed(PostingDate) THEN
        +        ERROR(DateNotAllowedErr,Caption,EntryNo);
        +    IF PostingDate > MaxPostingDate THEN
        +        MaxPostingDate := PostingDate;
         END;
         

        Bad code

        PROCEDURE IsReturned@14(EntryNo@1002 : Integer) : Decimal;
         VAR
        -ItemEntry@1000 : Record 32;
        -Quantity@1003 : Integer;
        +    ItemEntry@1000 : Record 32;
        +    Quantity@1003 : Integer;
         BEGIN
        -EXIT(-OutboundApplied(EntryNo,TRUE) - InboundApplied(EntryNo,TRUE));
        +    EXIT(-OutboundApplied(EntryNo,TRUE) - InboundApplied(EntryNo,TRUE));
         END;
         

        Good code

        PROCEDURE IsReturned@14(EntryNo@1002 : Integer) : Decimal;
         BEGIN
        -EXIT(-OutboundApplied(EntryNo,TRUE) - InboundApplied(EntryNo,TRUE));
        +    EXIT(-OutboundApplied(EntryNo,TRUE) - InboundApplied(EntryNo,TRUE));
         END;
         
        @@ -302,12 +302,12 @@ END;
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/variable-capacity-mismatch/index.html b/navpatterns/3-cal-coding-guidelines/design/variable-capacity-mismatch/index.html index 57eecfe2..4e9cf4eb 100644 --- a/navpatterns/3-cal-coding-guidelines/design/variable-capacity-mismatch/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/variable-capacity-mismatch/index.html @@ -5,16 +5,16 @@ Variable Capacity Mismatch :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -258,50 +258,50 @@ ALGuidelines.Dev

        Do not assign a value to a variable whose capacity is smaller.

        It will throw an error at runtime.

        Bad code

        -
        FileName@1010 : Text\[250\];
        +
        FileName@1010 : Text[250];
         ...
        -UploadedFileName@1016 : Text\[1024\];
        +UploadedFileName@1016 : Text[1024];
         ...
         FileName := UploadedFileName;
         

        Good code

        -
        FileName@1010 : Text\[1024\];
        +
        FileName@1010 : Text[1024];
         ...
        -UploadedFileName@1016 : Text\[1024\];
        +UploadedFileName@1016 : Text[1024];
         ...
         FileName := UploadedFileName;
         

        Bad code

        -
        FileName@1010 : Text\[250\];
        +
        FileName@1010 : Text[250];
         ...
        -UploadedFileName@1016 : Text\[1024\];
        +UploadedFileName@1016 : Text[1024];
         ...
         FileName := UploadedFileName;
         

        Good code

        -
        FileName@1010 : Text\[250\];
        +
        FileName@1010 : Text[250];
         ...
        -UploadedFileName@1016 : Text\[1024\];
        +UploadedFileName@1016 : Text[1024];
         ...
         FileName := COPYSTR(UploadedFileName,1,250); // In case only the first 250 chars are needed. Similar for fields
         

        Bad code

        VAR
        -ExceededNumberTxt@001 : 'ENU=Warning: Exceeded number of unsent documents/requests'
        -Subject@1002 : Text\[50\];
        -...
        +    ExceededNumberTxt@001 : 'ENU=Warning: Exceeded number of unsent documents/requests'
        +    Subject@1002 : Text[50];
        +    ...
         BEGIN
        -...
        -Subject := ExceededNumberTxt;
        +    ...
        +    Subject := ExceededNumberTxt;
         

        Good code

        VAR
        -ExceededNumberTxt@001 : 'ENU=Warning: Exceeded number of unsent documents/requests'
        -Subject@1002 : Text\[100\];
        -...
        +    ExceededNumberTxt@001 : 'ENU=Warning: Exceeded number of unsent documents/requests'
        +    Subject@1002 : Text[100];
        +    ...
         BEGIN
        -...
        -Subject := ExceededNumberTxt';
        +    ...
        +    Subject := ExceededNumberTxt';
         
        @@ -315,12 +315,12 @@ Subject := ExceededNumberTxt';
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/design/with-scope-name-collision/index.html b/navpatterns/3-cal-coding-guidelines/design/with-scope-name-collision/index.html index 8e5fd011..c4a0db09 100644 --- a/navpatterns/3-cal-coding-guidelines/design/with-scope-name-collision/index.html +++ b/navpatterns/3-cal-coding-guidelines/design/with-scope-name-collision/index.html @@ -5,16 +5,16 @@ WITH Scope Name Collision :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -256,25 +256,26 @@ ALGuidelines.Dev

        WITH Scope Name Collision

        Do not use the WITH scope when it has a variable whose name is the same as a local variable. This can lead to wrong code assumptions.

        -

        Given that “Contract Type” is a field on table ServiceContractHeader, then in the following example there is a parameter name clash with the field name. Which one will be used?

        +

        Given that
        +“Contract Type” is a field on table ServiceContractHeader, then in the following example there is a parameter name clash with the field name. Which one will be used?

        Bad code

        PROCEDURE InsertData@1("Contract Type"@1000 : Option...);
         ...
         BEGIN
        -...
        -WITH ServiceContractHeader DO BEGIN
        -...
        -DimMgt.InsertServContractDim(...,"Contract Type","Contract No.",0,...);
        +    ...
        +    WITH ServiceContractHeader DO BEGIN
        +        ...
        +        DimMgt.InsertServContractDim(...,"Contract Type","Contract No.",0,...);
         END;
         

        Good code

        PROCEDURE InsertData@1(ContractType@1000 : Option...);
         ...
         BEGIN
        -...
        -WITH ServiceContractHeader DO BEGIN
        -...
        -DimMgt.InsertServContractDim(...,ContractType,"Contract No.",0,...);
        +    ...
        +    WITH ServiceContractHeader DO BEGIN
        +        ...
        +        DimMgt.InsertServContractDim(...,ContractType,"Contract No.",0,...);
         END;
         
        @@ -289,12 +290,12 @@ END;
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/index.html b/navpatterns/3-cal-coding-guidelines/index.html index dce03e0e..9da2baee 100644 --- a/navpatterns/3-cal-coding-guidelines/index.html +++ b/navpatterns/3-cal-coding-guidelines/index.html @@ -6,16 +6,16 @@ 3. CAL Coding Guidelines :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -271,12 +271,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/internally-used-dot-net-types/index.html b/navpatterns/3-cal-coding-guidelines/internally-used-dot-net-types/index.html index e48459dd..f786535b 100644 --- a/navpatterns/3-cal-coding-guidelines/internally-used-dot-net-types/index.html +++ b/navpatterns/3-cal-coding-guidelines/internally-used-dot-net-types/index.html @@ -5,16 +5,16 @@ Internally used DotNet Types :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -367,12 +367,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/internationalization/index.html b/navpatterns/3-cal-coding-guidelines/internationalization/index.html index c2f1db0c..3df730af 100644 --- a/navpatterns/3-cal-coding-guidelines/internationalization/index.html +++ b/navpatterns/3-cal-coding-guidelines/internationalization/index.html @@ -6,16 +6,16 @@ Internationalization :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -275,12 +275,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/internationalization/index.xml b/navpatterns/3-cal-coding-guidelines/internationalization/index.xml index 34c1d294..3a66d182 100644 --- a/navpatterns/3-cal-coding-guidelines/internationalization/index.xml +++ b/navpatterns/3-cal-coding-guidelines/internationalization/index.xml @@ -1,3 +1,4 @@ -Internationalization on AL Guidelineshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/internationalization/Recent content in Internationalization on AL GuidelinesHugo -- gohugo.ioen-usUsing Calcdatehttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/internationalization/using-calcdate/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/internationalization/using-calcdate/CALCDATE should only be used with DateFormula variables. Alternatively the string should be enclosed using the &lt;&gt; symbols. Bad code -IF ReservEntry.&quot;Expected Receipt Date&quot; \&gt; CALCDATE('-' + FORMAT(&quot;Dampener (Time)&quot;) + FirstDate) THEN Good code -IF ReservEntry.&quot;Expected Receipt Date&quot; \&gt; CALCDATE('&lt;-' + FORMAT(&quot;Dampener (Time)&quot;) + FirstDate + '\&gt;') THEN \ No newline at end of file +Internationalization on AL Guidelineshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/internationalization/Recent content in Internationalization on AL GuidelinesHugo -- gohugo.ioen-usUsing Calcdatehttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/internationalization/using-calcdate/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/internationalization/using-calcdate/CALCDATE should only be used with DateFormula variables. Alternatively the string should be enclosed using the &lt;&gt; symbols. +Bad code +IF ReservEntry.&quot;Expected Receipt Date&quot; &gt; CALCDATE('-' + FORMAT(&quot;Dampener (Time)&quot;) + FirstDate) THEN Good code +IF ReservEntry.&quot;Expected Receipt Date&quot; &gt; CALCDATE('&lt;-' + FORMAT(&quot;Dampener (Time)&quot;) + FirstDate + '&gt;') THEN \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/internationalization/using-calcdate/index.html b/navpatterns/3-cal-coding-guidelines/internationalization/using-calcdate/index.html index 169d5368..7f08d1df 100644 --- a/navpatterns/3-cal-coding-guidelines/internationalization/using-calcdate/index.html +++ b/navpatterns/3-cal-coding-guidelines/internationalization/using-calcdate/index.html @@ -5,16 +5,16 @@ Using Calcdate :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -255,15 +255,15 @@ ALGuidelines.Dev

      Using Calcdate

      -

      CALCDATE should only be used with DateFormula variables. Alternatively the string should be enclosed using the <> symbols. -Bad code

      -
      IF ReservEntry."Expected Receipt Date" \>
      -CALCDATE('-' + FORMAT("Dampener (Time)") + FirstDate)
      +

      CALCDATE should only be used with DateFormula variables. Alternatively the string should be enclosed using the <> symbols.

      +

      Bad code

      +
      IF ReservEntry."Expected Receipt Date" >
      +    CALCDATE('-' + FORMAT("Dampener (Time)") + FirstDate)
       THEN
       

      Good code

      -
      IF ReservEntry."Expected Receipt Date" \>  
      -CALCDATE('<-' + FORMAT("Dampener (Time)") + FirstDate + '\>')  
      +
      IF ReservEntry."Expected Receipt Date" >  
      +    CALCDATE('<-' + FORMAT("Dampener (Time)") + FirstDate + '>')  
       THEN
       
      @@ -278,12 +278,12 @@ THEN
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/localizability/captionml-for-system-tables/index.html b/navpatterns/3-cal-coding-guidelines/localizability/captionml-for-system-tables/index.html index b91b7b01..518cc077 100644 --- a/navpatterns/3-cal-coding-guidelines/localizability/captionml-for-system-tables/index.html +++ b/navpatterns/3-cal-coding-guidelines/localizability/captionml-for-system-tables/index.html @@ -5,16 +5,16 @@ CaptionML on System Pages :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -287,12 +287,12 @@ OBJECT Table 2000000000 User
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/localizability/fieldcaption-and-tablecaption/index.html b/navpatterns/3-cal-coding-guidelines/localizability/fieldcaption-and-tablecaption/index.html index a88dfc4d..c5423026 100644 --- a/navpatterns/3-cal-coding-guidelines/localizability/fieldcaption-and-tablecaption/index.html +++ b/navpatterns/3-cal-coding-guidelines/localizability/fieldcaption-and-tablecaption/index.html @@ -5,16 +5,16 @@ FIELDCAPTION and TABLECAPTION :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -262,11 +262,10 @@ ALGuidelines.Dev
      • If the caption/name changes, then there will be a single point of change needed.
      • Bad code

        -
        IF NOT CONFIRM(UpdateLocationQst,TRUE,FIELDNAME("Location Code"),...)
        -
        -

        Good code

        -
        IF NOT CONFIRM(UpdateLocationQst,TRUE,FIELDCAPTION("Location Code"),...)
        -
        +
        IF NOT CONFIRM(UpdateLocationQst,TRUE,FIELDNAME("Location Code"),...)
        +

        Good code

        +
        IF NOT CONFIRM(UpdateLocationQst,TRUE,FIELDCAPTION("Location Code"),...)
        +
      @@ -279,12 +278,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/localizability/global-text-constants/index.html b/navpatterns/3-cal-coding-guidelines/localizability/global-text-constants/index.html index f2c79a96..895c63d4 100644 --- a/navpatterns/3-cal-coding-guidelines/localizability/global-text-constants/index.html +++ b/navpatterns/3-cal-coding-guidelines/localizability/global-text-constants/index.html @@ -5,16 +5,16 @@ Global Text Constants :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -257,18 +257,17 @@ ALGuidelines.Dev

        Global Text Constants

        Declare Text Constant as global variables.

        Bad code

        -
        PROCEDURE GetRequirementText@6(...) : Text\[50\];
        -VAR
        -RequirementOptionsTxt@1002 : TextConst 'ENU=Shipment,Receive,Pick,Put-Away';
        -BEGIN
        -
        -

        Good code

        -
        VAR
        -RequirementOptionsTxt@1002 : TextConst 'ENU=Shipment,Receive,Pick,Put-Away';
        -...
        -PROCEDURE GetRequirementText@6(...) : Text\[50\];
        -BEGIN
        -
        +
        PROCEDURE GetRequirementText@6(...) : Text[50];
        +VAR
        +    RequirementOptionsTxt@1002 : TextConst 'ENU=Shipment,Receive,Pick,Put-Away';
        +BEGIN
        +

        Good code

        +
        VAR
        +    RequirementOptionsTxt@1002 : TextConst 'ENU=Shipment,Receive,Pick,Put-Away';
        +...
        +PROCEDURE GetRequirementText@6(...) : Text[50];
        +BEGIN
        +
        @@ -281,12 +280,12 @@ BEGIN
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/localizability/index.html b/navpatterns/3-cal-coding-guidelines/localizability/index.html index 8232bd25..4314d769 100644 --- a/navpatterns/3-cal-coding-guidelines/localizability/index.html +++ b/navpatterns/3-cal-coding-guidelines/localizability/index.html @@ -6,16 +6,16 @@ Localizability :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -275,12 +275,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/localizability/index.xml b/navpatterns/3-cal-coding-guidelines/localizability/index.xml index ed89285d..8de767f3 100644 --- a/navpatterns/3-cal-coding-guidelines/localizability/index.xml +++ b/navpatterns/3-cal-coding-guidelines/localizability/index.xml @@ -4,15 +4,16 @@ Bad code ... { 2 ;2 ;Field ; CaptionML=ENU=Name; SourceExpr=Name } .FIELDCAPTION and TABLECAPTIONhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/fieldcaption-and-tablecaption/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/fieldcaption-and-tablecaption/For user messages, errors etc., use FIELDCAPTION not FIELDNAME and TABLECAPTION not TABLENAME. Reason: The correct translation will be automatically used. If the caption/name changes, then there will be a single point of change needed. Bad code -IF NOT CONFIRM(UpdateLocationQst,TRUE,FIELDNAME(&quot;Location Code&quot;),...) Good code -IF NOT CONFIRM(UpdateLocationQst,TRUE,FIELDCAPTION(&quot;Location Code&quot;),...)Global Text Constantshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/global-text-constants/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/global-text-constants/Declare Text Constant as global variables. +IF NOT CONFIRM(UpdateLocationQst,TRUE,FIELDNAME(&#34;Location Code&#34;),...) Good code +IF NOT CONFIRM(UpdateLocationQst,TRUE,FIELDCAPTION(&#34;Location Code&#34;),...)Global Text Constantshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/global-text-constants/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/global-text-constants/Declare Text Constant as global variables. Bad code -PROCEDURE GetRequirementText@6(...) : Text\[50\]; VAR RequirementOptionsTxt@1002 : TextConst 'ENU=Shipment,Receive,Pick,Put-Away'; BEGIN Good code -VAR RequirementOptionsTxt@1002 : TextConst 'ENU=Shipment,Receive,Pick,Put-Away'; ... PROCEDURE GetRequirementText@6(...) : Text\[50\]; BEGINUse Text Constantshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/use-text-constants/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/use-text-constants/Pass user messages using Text Constants. It makes translation easy. +PROCEDURE GetRequirementText@6(...) : Text[50]; VAR RequirementOptionsTxt@1002 : TextConst &#39;ENU=Shipment,Receive,Pick,Put-Away&#39;; BEGIN Good code +VAR RequirementOptionsTxt@1002 : TextConst &#39;ENU=Shipment,Receive,Pick,Put-Away&#39;; ... PROCEDURE GetRequirementText@6(...) : Text[50]; BEGINUse Text Constantshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/use-text-constants/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/use-text-constants/Pass user messages using Text Constants. It makes translation easy. Bad code -ImportAttachmentQst@1021 : TextConst 'ENU=&quot;Import attachment &quot;'; ... IF CONFIRM(ImportAttachmentQst + Caption +'?',TRUE) THEN BEGIN Good code -ImportAttachmentQst@1021 : TextConst 'ENU=&quot;Import attachment %1?&quot;'; ... IF CONFIRM(STRSUBSTNO(ImportAttachmentQst, Caption),TRUE) THEN BEGIN Bad code -... IF NOT CONFIRM( STRSUBSTNO( 'Difference on Periodic entries: %1 on %2' + 'Do you want to continue?',Balance,Date), TRUE) THEN ERROR('Program terminated by the user'); Good codeUsing OptionCaptionMLhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/using-optioncaptionml/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/using-optioncaptionml/The OptionCaptionML should be filled in for sourceexpression using option data types. +ImportAttachmentQst@1021 : TextConst &#39;ENU=&#34;Import attachment &#34;&#39;; ... IF CONFIRM(ImportAttachmentQst + Caption +&#39;?&#39;,TRUE) THEN BEGIN Good code +ImportAttachmentQst@1021 : TextConst &#39;ENU=&#34;Import attachment %1?&#34;&#39;; ... IF CONFIRM(STRSUBSTNO(ImportAttachmentQst, Caption),TRUE) THEN BEGIN Bad code +... IF NOT CONFIRM( STRSUBSTNO( &#39;Difference on Periodic entries: %1 on %2&#39; + &#39;Do you want to continue?&#39;,Balance,Date), TRUE) THEN ERROR(&#39;Program terminated by the user&#39;); Good code +DiffOnPeriodEntiesQst@100 : TextConst &#39;ENU=&#34;Difference on Periodic entries: %1 on %2\\ Do you want to continue?Using OptionCaptionMLhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/using-optioncaptionml/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/localizability/using-optioncaptionml/The OptionCaptionML should be filled in for sourceexpression using option data types. Bad code { 30 ;TextBox ;17850;0 ;150 ;423 ;Name=Selection; SourceExpr=Selection; DataSetFieldName=Selection } ... Selection@1008 : 'Open,Closed,Open and Closed'; ... Good code { 30 ;TextBox ;17850;0 ;150 ;423 ;Name=Selection; OptionCaptionML=ENU=Open,Closed,Open and Closed; SourceExpr=Selection; DataSetFieldName=Selection } ... Selection@1008 : 'Open,Closed,Open and Closed'; \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/localizability/use-text-constants/index.html b/navpatterns/3-cal-coding-guidelines/localizability/use-text-constants/index.html index e5aa7759..57c24dd2 100644 --- a/navpatterns/3-cal-coding-guidelines/localizability/use-text-constants/index.html +++ b/navpatterns/3-cal-coding-guidelines/localizability/use-text-constants/index.html @@ -5,16 +5,16 @@ Use Text Constants :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -257,33 +257,30 @@ ALGuidelines.Dev

        Use Text Constants

        Pass user messages using Text Constants. It makes translation easy.

        Bad code

        -
        ImportAttachmentQst@1021 : TextConst 'ENU="Import attachment "';
        -...
        -IF CONFIRM(ImportAttachmentQst + Caption +'?',TRUE) THEN BEGIN
        -
        -

        Good code

        -
        ImportAttachmentQst@1021 : TextConst 'ENU="Import attachment %1?"';
        -...
        -IF CONFIRM(STRSUBSTNO(ImportAttachmentQst, Caption),TRUE) THEN BEGIN
        -
        -

        Bad code

        -
        ...
        -IF NOT
        -CONFIRM(
        -STRSUBSTNO(
        -'Difference on Periodic entries: %1 on %2' +
        -'Do you want to continue?',Balance,Date),
        -TRUE)
        -THEN
        -ERROR('Program terminated by the user');
        -
        -

        Good code

        -
        DiffOnPeriodEntiesQst@100 : TextConst 'ENU="Difference on Periodic entries: %1 on %2\\ Do you want to continue?"';
        -ProgramTerminatedErr@200 : TextConst 'ENU="Program terminated by the user"';
        -...
        -IF NOT CONFIRM(STRSUBSTNO(DiffOnPeriodEntiesQst,Balance,Date),TRUE) THEN
        -ERROR(ProgramTerminatedErr);
        -
        +
        ImportAttachmentQst@1021 : TextConst 'ENU="Import attachment "';
        +...
        +IF CONFIRM(ImportAttachmentQst + Caption +'?',TRUE) THEN BEGIN
        +

        Good code

        +
        ImportAttachmentQst@1021 : TextConst 'ENU="Import attachment %1?"';
        +...
        +IF CONFIRM(STRSUBSTNO(ImportAttachmentQst, Caption),TRUE) THEN BEGIN
        +

        Bad code

        +
        ...
        +    IF NOT
        +        CONFIRM(
        +            STRSUBSTNO(
        +                'Difference on Periodic entries: %1 on %2' +
        +                'Do you want to continue?',Balance,Date),
        +            TRUE)
        +    THEN
        +        ERROR('Program terminated by the user');
        +

        Good code

        +
        DiffOnPeriodEntiesQst@100 : TextConst 'ENU="Difference on Periodic entries: %1 on %2\\ Do you want to continue?"';
        +ProgramTerminatedErr@200 : TextConst 'ENU="Program terminated by the user"';
        +...
        +    IF NOT CONFIRM(STRSUBSTNO(DiffOnPeriodEntiesQst,Balance,Date),TRUE) THEN
        +        ERROR(ProgramTerminatedErr);
        +
        @@ -296,12 +293,12 @@ ERROR(ProgramTerminatedErr);
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/localizability/using-optioncaptionml/index.html b/navpatterns/3-cal-coding-guidelines/localizability/using-optioncaptionml/index.html index cbcd5d09..6c4d46bd 100644 --- a/navpatterns/3-cal-coding-guidelines/localizability/using-optioncaptionml/index.html +++ b/navpatterns/3-cal-coding-guidelines/localizability/using-optioncaptionml/index.html @@ -5,16 +5,16 @@ Using OptionCaptionML :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -284,12 +284,12 @@ Selection@1008 : 'Open,Closed,Open and Closed';
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/begin-as-an-afterword/index.html b/navpatterns/3-cal-coding-guidelines/readability/begin-as-an-afterword/index.html index 8694fadc..de926ee8 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/begin-as-an-afterword/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/begin-as-an-afterword/index.html @@ -5,16 +5,16 @@ Begin as an 'After Word' :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -255,15 +255,15 @@ ALGuidelines.Dev

      Begin as an 'After Word'

      -

      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
      -...
      +

      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;
       
      @@ -278,12 +278,12 @@ END;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/begin-end/index.html b/navpatterns/3-cal-coding-guidelines/readability/begin-end/index.html index fd30cdb3..dc95dc6b 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/begin-end/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/begin-end/index.html @@ -5,16 +5,16 @@ Begin-End - Compound Only :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,50 +255,44 @@ ALGuidelines.Dev

      Begin-End - Compound Only

      -

      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;
      -
      +

      Only use BEGIN..END to enclose compound statements.

      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)
      -
      +
      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)
      +
      @@ -311,12 +305,12 @@ END ELSE (not X)
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/binary-operator-line-start/index.html b/navpatterns/3-cal-coding-guidelines/readability/binary-operator-line-start/index.html index ef179891..51d8d94a 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/binary-operator-line-start/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/binary-operator-line-start/index.html @@ -5,16 +5,16 @@ Binary Operator to Start Line :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,16 +255,16 @@ ALGuidelines.Dev

      Binary Operator to Start Line

      -

      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"
      -
      +

      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"
      +
      @@ -277,12 +277,12 @@ Quantity -
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/blank-lines/index.html b/navpatterns/3-cal-coding-guidelines/readability/blank-lines/index.html index fb1e419a..25dae574 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/blank-lines/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/blank-lines/index.html @@ -5,16 +5,16 @@ Blank Lines :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,30 +255,31 @@ ALGuidelines.Dev

      Blank Lines

      -

      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;
      -
      +

      Do not use blank lines at the beginning or end of any functions, after BEGIN, before END, or inside multiline expressions.

      Bad code

      -
      IF NameIsValid AND
      -Name2IsValid
      -THEN
      -
      -

      Good code

      -
      IF NameIsValid AND
      -Name2IsValid
      -THEN
      -
      +
      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
      +
      @@ -291,12 +292,12 @@ THEN
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/case-actions/index.html b/navpatterns/3-cal-coding-guidelines/readability/case-actions/index.html index a7e7e30a..3fde51a0 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/case-actions/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/case-actions/index.html @@ -5,16 +5,16 @@ CASE Action :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,18 +255,18 @@ ALGuidelines.Dev

      CASE Action

      -

      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';
      -
      +

      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';
      +
      @@ -279,12 +279,12 @@ Letter2 := '11';
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/colon-usage-in-case/index.html b/navpatterns/3-cal-coding-guidelines/readability/colon-usage-in-case/index.html index cbb814c0..bfd97eba 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/colon-usage-in-case/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/colon-usage-in-case/index.html @@ -5,16 +5,16 @@ Colon usage in CASE :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,16 +255,16 @@ ALGuidelines.Dev

      Colon usage in CASE

      -

      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";
      -
      +

      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";
      +
      @@ -277,12 +277,12 @@ DimValue."Dimension Code" := GLSetup."Global Dimension 1 Code&quo
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/comments-curly-brackets/index.html b/navpatterns/3-cal-coding-guidelines/readability/comments-curly-brackets/index.html index 1abd7cd1..d4476d11 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/comments-curly-brackets/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/comments-curly-brackets/index.html @@ -5,16 +5,16 @@ Comments inside Curly Brackets :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,34 +255,32 @@ ALGuidelines.Dev

      Comments inside Curly Brackets

      -

      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  
      -
      +

      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

      -
      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;
      -
      +
      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;
      +
      @@ -295,12 +293,12 @@ END;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/comments-spacing/index.html b/navpatterns/3-cal-coding-guidelines/readability/comments-spacing/index.html index 18c9f84c..fb1529c8 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/comments-spacing/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/comments-spacing/index.html @@ -5,16 +5,16 @@ Comment Spacing :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,13 +255,12 @@ ALGuidelines.Dev

      Comment Spacing

      -

      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
      -
      +

      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
      +
      @@ -274,12 +273,12 @@ Bad code

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/end-else-pair/index.html b/navpatterns/3-cal-coding-guidelines/readability/end-else-pair/index.html index acfaf783..59e4086c 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/end-else-pair/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/end-else-pair/index.html @@ -5,16 +5,16 @@ END ELSE Pair :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,22 +255,21 @@ ALGuidelines.Dev

      END ELSE Pair

      -

      The END ELSE pair should always appear on the same line. -Bad code

      -
      IF OppEntry.FIND('-') THEN
      -IF SalesCycleStage.FIND('-') THEN BEGIN
      -...
      -END
      -ELSE
      -...
      -
      -

      Good code

      -
      IF OppEntry.FIND('-') THEN
      -IF SalesCycleStage.FIND('-') THEN BEGIN
      -...
      -END ELSE
      -...
      -
      +

      The END ELSE pair should always appear on the same line.

      +

      Bad code

      +
      IF OppEntry.FIND('-') THEN
      +    IF SalesCycleStage.FIND('-') THEN BEGIN
      +        ...
      +    END
      +ELSE
      +    ...
      +

      Good code

      +
      IF OppEntry.FIND('-') THEN
      +    IF SalesCycleStage.FIND('-') THEN BEGIN
      +        ...
      +END ELSE
      +    ...
      +
      @@ -283,12 +282,12 @@ END ELSE
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/indentation/index.html b/navpatterns/3-cal-coding-guidelines/readability/indentation/index.html index 5455e6af..a7979eba 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/indentation/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/indentation/index.html @@ -5,16 +5,16 @@ Indentation :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,72 +255,59 @@ ALGuidelines.Dev

      Indentation

      -

      In general, use an indentation of two space characters. Logical expressions in the IF, WHILE, and UNTIL parts are indented at least 3, 6, and 6 spaces respectively. -Bad code

      -
      IF GLSetup."Unrealized VAT" OR
      -(GLSetup."Prepayment Unrealized VAT" AND NewCVLedgEntryBuf.Prepayment)
      -
      -

      Good code

      -
      IF GLSetup."Unrealized VAT" OR
      -(GLSetup."Prepayment Unrealized VAT" AND NewCVLedgEntryBuf.Prepayment)
      -
      +

      In general, use an indentation of two space characters. Logical expressions in the IF, WHILE, and UNTIL parts are indented at least 3, 6, and 6 spaces respectively.

      Bad code

      -
      IF GenJnlLine."Account No." <\> ICPartner.Code THEN
      -ICPartner.GET("Account No.");
      -IF GenJnlLine.Amount \> 0 THEN BEGIN
      -...
      -
      -

      Good code

      -
      IF GenJnlLine."Account No." <\> ICPartner.Code THEN
      -ICPartner.GET("Account No.");
      -IF GenJnlLine.Amount \> 0 THEN BEGIN
      -...
      -
      -

      Bad code

      -
      Dialog.OPEN(WindowTxt +
      -'@1@@@@@@@@@@@@@@@@@@@@@@@');
      -
      -

      Good code

      -
      Dialog.OPEN(
      -WindowTxt +
      -'@1@@@@@@@@@@@@@@@@@@@@@@@');
      -
      -

      Bad code

      -
      TempOldCustLedgEntry.DELETE;
      -// Find the next old entry for application of the new entry
      -
      -

      Good code

      -
      TempOldCustLedgEntry.DELETE;
      -// Find the next old entry for application of the new entry
      -
      -

      Bad code

      -
      IF NOT ("Applies-to Doc. Type" IN
      -\["Applies-to Doc. Type"::Receipt,
      -"Applies-to Doc. Type"::"Return Shipment"\])
      -
      -

      Good code

      -
      IF NOT ("Applies-to Doc. Type" IN
      -\["Applies-to Doc. Type"::Receipt,
      -"Applies-to Doc. Type"::"Return Shipment"\])
      -
      -

      Bad code

      -
      WHILE (RemAmt \> 0) OR
      -(RemAmtLCY \> 0)
      -DO
      -
      -

      Good code

      -
      WHILE (RemAmt \> 0) OR
      -(RemAmtLCY \> 0)
      -DO
      -
      -

      Bad code

      -
      UNTIL (RemAmt \> 0) AND
      -(RemAmtLCY \> 0);
      -
      -

      Good code

      -
      UNTIL (RemAmt \> 0) AND
      -(RemAmtLCY \> 0)
      -
      +
      IF GLSetup."Unrealized VAT" OR
      +  (GLSetup."Prepayment Unrealized VAT" AND NewCVLedgEntryBuf.Prepayment)
      +

      Good code

      +
      IF GLSetup."Unrealized VAT" OR
      +    (GLSetup."Prepayment Unrealized VAT" AND NewCVLedgEntryBuf.Prepayment)
      +

      Bad code

      +
      IF GenJnlLine."Account No." <> ICPartner.Code THEN
      +  ICPartner.GET("Account No.");
      +  IF GenJnlLine.Amount \> 0 THEN BEGIN
      +    ...
      +

      Good code

      +
      IF GenJnlLine."Account No." <> ICPartner.Code THEN
      +  ICPartner.GET("Account No.");
      +IF GenJnlLine.Amount > 0 THEN BEGIN
      +  ...
      +

      Bad code

      +
      Dialog.OPEN(WindowTxt +
      +            '@1@@@@@@@@@@@@@@@@@@@@@@@');
      +

      Good code

      +
      Dialog.OPEN(
      +    WindowTxt +
      +    '@1@@@@@@@@@@@@@@@@@@@@@@@');
      +

      Bad code

      +
      TempOldCustLedgEntry.DELETE;
      +  // Find the next old entry for application of the new entry
      +

      Good code

      +
      TempOldCustLedgEntry.DELETE;
      +// Find the next old entry for application of the new entry
      +

      Bad code

      +
      IF NOT ("Applies-to Doc. Type" IN
      +   ["Applies-to Doc. Type"::Receipt,
      +    "Applies-to Doc. Type"::"Return Shipment"])
      +

      Good code

      +
      IF NOT ("Applies-to Doc. Type" IN
      +        ["Applies-to Doc. Type"::Receipt,
      +         "Applies-to Doc. Type"::"Return Shipment"])
      +

      Bad code

      +
      WHILE (RemAmt > 0) OR
      +  (RemAmtLCY > 0)
      +DO
      +

      Good code

      +
      WHILE (RemAmt > 0) OR
      +      (RemAmtLCY > 0)
      +DO
      +

      Bad code

      +
      UNTIL (RemAmt > 0) AND
      +  (RemAmtLCY > 0);
      +

      Good code

      +
      UNTIL (RemAmt > 0) AND
      +      (RemAmtLCY > 0)
      +
      @@ -333,12 +320,12 @@ DO
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/index.html b/navpatterns/3-cal-coding-guidelines/readability/index.html index 34cb1b9a..97d1e077 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/index.html @@ -6,16 +6,16 @@ Readability :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev - - - + + +
        @@ -276,12 +276,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/index.xml b/navpatterns/3-cal-coding-guidelines/readability/index.xml index 258c2d4b..bf0c1521 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/index.xml +++ b/navpatterns/3-cal-coding-guidelines/readability/index.xml @@ -1,63 +1,84 @@ -Readability on AL Guidelineshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/Recent content in Readability on AL GuidelinesHugo -- gohugo.ioen-usBegin as an 'After Word'https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/begin-as-an-afterword/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/begin-as-an-afterword/When BEGIN follows THEN, ELSE, DO, it should be on the same line, preceded by one space character. Bad code -IF ICPartnerRefType = ICPartnerRefType::&quot;Common Item No.&quot; THEN BEGIN ... END; Good code -IF ICPartnerRefType = ICPartnerRefType::&quot;Common Item No.&quot; THEN BEGIN ... END;Begin-End - Compound Onlyhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/begin-end/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/begin-end/Only use BEGIN..END to enclose compound statements. Bad code +Readability on AL Guidelineshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/Recent content in Readability on AL GuidelinesHugo -- gohugo.ioen-usBegin as an 'After Word'https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/begin-as-an-afterword/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/begin-as-an-afterword/When BEGIN follows THEN, ELSE, DO, it should be on the same line, preceded by one space character. +Bad code +IF ICPartnerRefType = ICPartnerRefType::&#34;Common Item No.&#34; THEN BEGIN ... END; Good code +IF ICPartnerRefType = ICPartnerRefType::&quot;Common Item No.&quot; THEN BEGIN ... END;Begin-End - Compound Onlyhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/begin-end/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/begin-end/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(&quot;Order Line No.&quot;,0); END; Good code -IF IsAssemblyOutputLine THEN TESTFIELD(&quot;Order Line No.&quot;,0); Bad code +IF IsAssemblyOutputLine THEN BEGIN TESTFIELD(&#34;Order Line No.&#34;,0); END; Good code +IF IsAssemblyOutputLine THEN TESTFIELD(&#34;Order Line No.&#34;,0); Bad code IF FINDSET THEN REPEAT BEGIN ... END; UNTIL NEXT = 0; Good code -IF FINDSET THEN REPEAT .Binary Operator to Start Linehttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/binary-operator-line-start/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/binary-operator-line-start/Do not start a line with a binary operator. Bad code -&quot;Quantity to Ship&quot; := Quantity - &quot;Quantity Shipped&quot; Good code -&quot;Quantity to Ship&quot; := Quantity - &quot;Quantity Shipped&quot;Blank Lineshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/blank-lines/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/blank-lines/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 FINDSET THEN REPEAT ... UNTIL NEXT = 0; ExceptionBinary Operator to Start Linehttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/binary-operator-line-start/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/binary-operator-line-start/Do not start a line with a binary operator. +Bad code +&#34;Quantity to Ship&#34; := Quantity - &#34;Quantity Shipped&#34; Good code +&#34;Quantity to Ship&#34; := Quantity - &#34;Quantity Shipped&#34;Blank Lineshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/blank-lines/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/blank-lines/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 THENCASE Actionhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/case-actions/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/case-actions/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';Colon usage in CASEhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/colon-usage-in-case/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/colon-usage-in-case/The last possibility on a CASE statement must be immediately followed by a colon. Bad code -CASE DimOption OF DimOption::&quot;Global Dimension 1&quot; : DimValue.&quot;Dimension Code&quot; := GLSetup.&quot;Global Dimension 1 Code&quot;; Good code -CASE DimOption OF DimOption::&quot;Global Dimension 1&quot;: DimValue.&quot;Dimension Code&quot; := GLSetup.&quot;Global Dimension 1 Code&quot;;Comments inside Curly Bracketshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/comments-curly-brackets/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/comments-curly-brackets/Never use curly bracket comments. During development, the &ldquo;Block comment&rdquo; functionality can be used instead. However, in production code, block comments are not recommended. Bad code +IF NameIsValid AND Name2IsValid THENCASE Actionhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/case-actions/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/case-actions/A CASE action should start on a line after the possibility. +Bad code +CASE Letter OF &#39;A&#39;: Letter2 := &#39;10&#39;; &#39;B&#39;: Letter2 := &#39;11&#39;; Good code +CASE Letter OF &#39;A&#39;: Letter2 := &#39;10&#39;; &#39;B&#39;: Letter2 := &#39;11&#39;;Colon usage in CASEhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/colon-usage-in-case/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/colon-usage-in-case/The last possibility on a CASE statement must be immediately followed by a colon. +Bad code +CASE DimOption OF DimOption::&#34;Global Dimension 1&#34; : DimValue.&#34;Dimension Code&#34; := GLSetup.&#34;Global Dimension 1 Code&#34;; Good code +CASE DimOption OF DimOption::&#34;Global Dimension 1&#34;: DimValue.&#34;Dimension Code&#34; := GLSetup.&#34;Global Dimension 1 Code&#34;;Comments inside Curly Bracketshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/comments-curly-brackets/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/comments-curly-brackets/Never use curly bracket comments. During development, the &ldquo;Block comment&rdquo; 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;Comment Spacinghttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/comments-spacing/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/comments-spacing/Always start comments with // followed by one space character. 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;Comment Spacinghttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/comments-spacing/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/comments-spacing/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 budgetEND ELSE Pairhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/end-else-pair/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/end-else-pair/The END ELSE pair should always appear on the same line. Bad code -IF OppEntry.FIND('-') THEN IF SalesCycleStage.FIND('-') THEN BEGIN ... END ELSE ... Good code -IF OppEntry.FIND('-') THEN IF SalesCycleStage.FIND('-') THEN BEGIN ... END ELSE ...Indentationhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/indentation/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/indentation/In general, use an indentation of two space characters. Logical expressions in the IF, WHILE, and UNTIL parts are indented at least 3, 6, and 6 spaces respectively. Bad code -IF GLSetup.&quot;Unrealized VAT&quot; OR (GLSetup.&quot;Prepayment Unrealized VAT&quot; AND NewCVLedgEntryBuf.Prepayment) Good code -IF GLSetup.&quot;Unrealized VAT&quot; OR (GLSetup.&quot;Prepayment Unrealized VAT&quot; AND NewCVLedgEntryBuf.Prepayment) Bad code -IF GenJnlLine.&quot;Account No.&quot; &lt;\&gt; ICPartner.Code THEN ICPartner.GET(&quot;Account No.&quot;); IF GenJnlLine.Amount \&gt; 0 THEN BEGIN ... Good codeKeyword Pairs - Indentationhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/keyword-pairs-indentation/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/keyword-pairs-indentation/The IF..THEN pair, WHILE..DO pair, and FOR..DO pair must appear on the same line or the same level of indentation. Bad code +RowNo += 1000; // Move way below the budgetEND ELSE Pairhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/end-else-pair/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/end-else-pair/The END ELSE pair should always appear on the same line. +Bad code +IF OppEntry.FIND(&#39;-&#39;) THEN IF SalesCycleStage.FIND(&#39;-&#39;) THEN BEGIN ... END ELSE ... Good code +IF OppEntry.FIND(&#39;-&#39;) THEN IF SalesCycleStage.FIND(&#39;-&#39;) THEN BEGIN ... END ELSE ...Indentationhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/indentation/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/indentation/In general, use an indentation of two space characters. Logical expressions in the IF, WHILE, and UNTIL parts are indented at least 3, 6, and 6 spaces respectively. +Bad code +IF GLSetup.&#34;Unrealized VAT&#34; OR (GLSetup.&#34;Prepayment Unrealized VAT&#34; AND NewCVLedgEntryBuf.Prepayment) Good code +IF GLSetup.&#34;Unrealized VAT&#34; OR (GLSetup.&#34;Prepayment Unrealized VAT&#34; AND NewCVLedgEntryBuf.Prepayment) Bad code +IF GenJnlLine.&#34;Account No.&#34; &lt;&gt; ICPartner.Code THEN ICPartner.GET(&#34;Account No.&#34;); IF GenJnlLine.Amount \&gt; 0 THEN BEGIN ... Good code +IF GenJnlLine.Keyword Pairs - Indentationhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/keyword-pairs-indentation/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/keyword-pairs-indentation/The IF..THEN pair, WHILE..DO pair, and FOR..DO pair must appear on the same line or the same level of indentation. +Bad code IF (x = y) AND (a = b) THEN Good code -IF (x = y) AND (a = b) THENLine Start Keywordshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/line-start-keywords/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/line-start-keywords/The END, IF, REPEAT, FOR, WHILE, ELSE and CASE statement should always start a line. Bad code +IF (x = y) AND (a = b) THENLine Start Keywordshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/line-start-keywords/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/line-start-keywords/The END, IF, REPEAT, FOR, WHILE, ELSE and CASE statement should always start a line. +Bad code IF IsContactName THEN ValidateContactName ELSE IF IsSalespersonCode THEN ValidateSalespersonCode ELSE IF IsSalesCycleCode THEN ValidatSalesCycleCode; Good code -IF IsContactName THEN ValidateContactName ELSE IF IsSalespersonCode THEN ValidateSalespersonCode ELSE IF IsSalesCycleCode THEN ValidatSalesCycleCode;Lonely Repeathttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/lonely-repeat/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/lonely-repeat/The REPEAT statement should always be alone on a line. Bad code +IF IsContactName THEN ValidateContactName ELSE IF IsSalespersonCode THEN ValidateSalespersonCode ELSE IF IsSalesCycleCode THEN ValidatSalesCycleCode;Lonely Repeathttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/lonely-repeat/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/lonely-repeat/The REPEAT statement should always be alone on a line. +Bad code IF ReservEntry.FINDSET THEN REPEAT Good code -IF ReservEntry.FINDSET THEN REPEATNamed Invocationshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/named-invocations/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/named-invocations/When calling an object statically use the name, not the number Bad code +IF ReservEntry.FINDSET THEN REPEATNamed Invocationshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/named-invocations/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/named-invocations/When calling an object statically use the name, not the number +Bad code PAGE.RUNMODAL(525,SalesShptLine) Good code -PAGE.RUNMODAL(PAGE::&quot;Posted Sales Shipment Lines&quot;,SalesShptLine)Nested WITHshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/nested-withs/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/nested-withs/Do not nest WITHs that reference different types of objects. Bad code -WITH PostedWhseShptLine DO BEGIN ... WITH ItemLedgEntry DO InsertBufferRec(...,&quot;Serial No.&quot;,&quot;Lot No.&quot;,...); ... END; Good code -WITH PostedWhseShptLine DO BEGIN ... InsertBufferRec(...,ItemLedgEntry.&quot;Serial No.&quot;,ItemLedgEntry.&quot;Lot No.&quot;,...); ... END;One Statement Per Linehttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/one-statement-per-line/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/one-statement-per-line/A line of code should not have more than one statement. Bad code -IF OppEntry.FIND('-') THEN EXIT Good code -IF OppEntry.FIND('-') THEN EXIT Bad code +PAGE.RUNMODAL(PAGE::&#34;Posted Sales Shipment Lines&#34;,SalesShptLine)Nested WITHshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/nested-withs/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/nested-withs/Do not nest WITHs that reference different types of objects. +Bad code +WITH PostedWhseShptLine DO BEGIN ... WITH ItemLedgEntry DO InsertBufferRec(...,&#34;Serial No.&#34;,&#34;Lot No.&#34;,...); ... END; Good code +WITH PostedWhseShptLine DO BEGIN ... InsertBufferRec(...,ItemLedgEntry.&#34;Serial No.&#34;,ItemLedgEntry.&#34;Lot No.&#34;,...); ... END;One Statement Per Linehttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/one-statement-per-line/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/one-statement-per-line/A line of code should not have more than one statement. +Bad code +IF OppEntry.FIND(&#39;-&#39;) THEN EXIT Good code +IF OppEntry.FIND(&#39;-&#39;) THEN EXIT Bad code TotalCost += Cost; TotalAmt += Amt; Good code -TotalCost += Cost; TotalAmt += Amt;Separate IF and ELSEhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/separate-if-and-else/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/separate-if-and-else/IF and ELSE statements should be on separate lines. Bad code -IF Atom\[i+1\] = '\&gt;' THEN HasLogicalOperator := TRUE ELSE BEGIN ... END; Good code -IF Atom\[i+1\] = '\&gt;' THEN HasLogicalOperator := TRUE ELSE BEGIN ... END;Spacing Binary Operatorshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-binary-operators/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-binary-operators/There must be exactly one space character on each side of a binary operator such as = + - AND OR =. The parameter comma operator however, should have no spaces. Bad code -&quot;Line Discount %&quot; := &quot;Line Discount Amount&quot;/&quot;Line Value&quot;\*100 Good code -&quot;Line Discount %&quot; := &quot;Line Discount Amount&quot; / &quot;Line Value&quot; \* 100; Bad code -StartDate := CALCDATE('&lt;+'+FORMAT(Days + i)+'D\&gt;', StartDate); Good code -StartDate := CALCDATE('&lt;+' + FORMAT(Days + i) + 'D\&gt;',StartDate); Bad codeSpacing Brackets and ::https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-brackets-and/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-brackets-and/There must be no spaces characters before and after [] dimension brackets symbols or :: option symbols. Bad code -A\[i\] \[j\] := Amt; Good code -A\[i\]\[j\] := Amt; Bad code -&quot;Currency Exchange Rate&quot;.&quot;Fix Exchange Rate Amount&quot; :: Currency: Good code -&quot;Currency Exchange Rate&quot;.&quot;Fix Exchange Rate Amount&quot;::Currency: Bad code +TotalCost += Cost; TotalAmt += Amt;Separate IF and ELSEhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/separate-if-and-else/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/separate-if-and-else/IF and ELSE statements should be on separate lines. +Bad code +IF Atom[i+1] = &#39;&gt;&#39; THEN HasLogicalOperator := TRUE ELSE BEGIN ... END; Good code +IF Atom[i+1] = &#39;&gt;&#39; THEN HasLogicalOperator := TRUE ELSE BEGIN ... END;Spacing Binary Operatorshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-binary-operators/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-binary-operators/There must be exactly one space character on each side of a binary operator such as = + - AND OR =. The parameter comma operator however, should have no spaces. +Bad code +&#34;Line Discount %&#34; := &#34;Line Discount Amount&#34;/&#34;Line Value&#34;*100 Good code +&#34;Line Discount %&#34; := &#34;Line Discount Amount&#34; / &#34;Line Value&#34; * 100; Bad code +StartDate := CALCDATE(&#39;&lt;+&#39;+FORMAT(Days + i)+&#39;D&gt;&#39;, StartDate); Good code +StartDate := CALCDATE(&#39;&lt;+&#39; + FORMAT(Days + i) + &#39;D&gt;&#39;,StartDate); Bad codeSpacing Brackets and ::https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-brackets-and/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-brackets-and/There must be no spaces characters before and after [] dimension brackets symbols or :: option symbols. +Bad code +A[i] [j] := Amt; Good code +A[i][j] := Amt; Bad code +&#34;Currency Exchange Rate&#34;.&#34;Fix Exchange Rate Amount&#34; :: Currency: Good code +&#34;Currency Exchange Rate&#34;.&#34;Fix Exchange Rate Amount&#34;::Currency: Bad code IF FIND (Which) THEN Good code -IF FIND(Which) THENSpacing Unary Operatorshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-unary-operators/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-unary-operators/There must be no space between a unary operator and its argument (except for the NOT keyword). Bad code +IF FIND(Which) THENSpacing Unary Operatorshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-unary-operators/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/spacing-unary-operators/There must be no space between a unary operator and its argument (except for the NOT keyword). +Bad code IF NOT(Type = Type::Item) THEN Good code IF NOT (Type = Type::Item) THEN Bad code -DiscAmt := - &quot;Discount Amount&quot;; Good code -DiscAmt := -&quot;Discount Amount&quot;;Suggested Abbreviationshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/suggested-abbreviations/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/suggested-abbreviations/Suggested Abbreviations Whenever possible, do not use abbreviations in variables, functions and objects names. +DiscAmt := - &#34;Discount Amount&#34;; Good code +DiscAmt := -&#34;Discount Amount&#34;;Suggested Abbreviationshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/suggested-abbreviations/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/suggested-abbreviations/Suggested Abbreviations Whenever possible, do not use abbreviations in variables, functions and objects names. If there is no other choice, then use the suggestions below. Abbreviation Text @@ -103,38 +124,49 @@ Appln application Arriv arrival -AsmTemporary Variable Naminghttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/temporary-variable-naming/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/temporary-variable-naming/The name of a temporary variable must be prefixed with the word Temp and not otherwise. Bad code +AsmTemporary Variable Naminghttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/temporary-variable-naming/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/temporary-variable-naming/The name of a temporary variable must be prefixed with the word Temp and not otherwise. +Bad code JobWIPBuffer@1002 : TEMPORARY Record 1018; Good code TempJobWIPBuffer@1002 : TEMPORARY Record 1018; Bad code TempJobWIPBuffer@1002 : Record 1018; Good code -CopyOfJobWIPBuffer@1002 : Record 1018;TextConst Suffixeshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/textconst-suffixes/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/textconst-suffixes/TextConst variable names should have a suffix (an approved three-letter suffix: Msg, Tok, Err, Qst, Lbl, Txt) describing usage. Bad code -CannotDeleteLine@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.'; ... ERROR(CannotDeleteLine,TABLECAPTION); Good code -CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.'; ... ERROR(CannotDeleteLineErr,TABLECAPTION); Bad code -Text000@1011 : TextConst 'ENU=&quot;has been changed (initial a %1: %2= %3, %4= %5)&quot;'; .Unary Operator Line Endhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unary-operator-line-end/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unary-operator-line-end/Do not end a line with unary operator. Bad code -&quot;Quantity Handled (Base)&quot; := - &quot;Quantity Handled (Base)&quot;); Good code -&quot;Quantity Handled (Base)&quot; := - &quot;Quantity Handled (Base)&quot;);Unnecessary Compound Parenthesishttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-compound-parenthesis/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-compound-parenthesis/Use parenthesis only to enclose compound expressions inside compound expressions. Bad code -IF (&quot;Costing Method&quot; = &quot;Costing Method&quot;::Standard) THEN Good code -IF &quot;Costing Method&quot; = &quot;Costing Method&quot;::Standard THEN Bad code -ProfitPct = -(Profit) / CostAmt \* 100; Good code -ProfitPct = -Profit / CostAmt \* 100;Unnecessary ELSEhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-else/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-else/ELSE should not be used when the last action in the THEN part is an EXIT, BREAK, SKIP, QUIT, ERROR. Bad code +CopyOfJobWIPBuffer@1002 : Record 1018;TextConst Suffixeshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/textconst-suffixes/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/textconst-suffixes/TextConst variable names should have a suffix (an approved three-letter suffix: Msg, Tok, Err, Qst, Lbl, Txt) describing usage. +Bad code +CannotDeleteLine@1005 : TextConst &#39;ENU=You cannot delete this line because one or more rating values exists.&#39;; ... ERROR(CannotDeleteLine,TABLECAPTION); Good code +CannotDeleteLineErr@1005 : TextConst &#39;ENU=You cannot delete this line because one or more rating values exists.&#39;; ... ERROR(CannotDeleteLineErr,TABLECAPTION); Bad code +Text000@1011 : TextConst &#39;ENU=&#34;has been changed (initial a %1: %2= %3, %4= %5)&#34;&#39;; .Unary Operator Line Endhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unary-operator-line-end/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unary-operator-line-end/Do not end a line with unary operator. +Bad code +&#34;Quantity Handled (Base)&#34; := - &#34;Quantity Handled (Base)&#34;); Good code +&#34;Quantity Handled (Base)&#34; := - &#34;Quantity Handled (Base)&#34;);Unnecessary Compound Parenthesishttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-compound-parenthesis/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-compound-parenthesis/Use parenthesis only to enclose compound expressions inside compound expressions. +Bad code +IF (&#34;Costing Method&#34; = &#34;Costing Method&#34;::Standard) THEN Good code +IF &#34;Costing Method&#34; = &#34;Costing Method&#34;::Standard THEN Bad code +ProfitPct = -(Profit) / CostAmt * 100; Good code +ProfitPct = -Profit / CostAmt * 100;Unnecessary ELSEhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-else/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-else/ELSE should not be used when the last action in the THEN part is an EXIT, BREAK, SKIP, QUIT, ERROR. +Bad code IF IsAdjmtBinCodeChanged THEN ERROR(AdjmtBinCodeChangeNotAllowedErr,...) ELSE ERROR(BinCodeChangeNotAllowedErr,...); Good code -IF IsAdjmtBinCodeChanged THEN ERROR(AdjmtBinCodeChangeNotAllowedErr,...) ERROR(BinCodeChangeNotAllowedErr,...);Unnecessary Function Parenthesishttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-function-parenthesis/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-function-parenthesis/Do not use parenthesis in a function call if the function does not have any parameters. Bad code +IF IsAdjmtBinCodeChanged THEN ERROR(AdjmtBinCodeChangeNotAllowedErr,...) ERROR(BinCodeChangeNotAllowedErr,...);Unnecessary Function Parenthesishttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-function-parenthesis/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-function-parenthesis/Do not use parenthesis in a function call if the function does not have any parameters. +Bad code IF ReservMgt.IsPositive() THEN Good code IF ReservMgt.IsPositive THEN Bad code -IF ChangeStatusForm.RUNMODAL() &lt;\&gt; ACTION::Yes THEN Good code -IF ChangeStatusForm.RUNMODAL &lt;\&gt; ACTION::Yes THENUnnecessary Separatorshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-separators/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-separators/There should be no unnecessary separators. Bad code +IF ChangeStatusForm.RUNMODAL() &lt;&gt; ACTION::Yes THEN Good code +IF ChangeStatusForm.RUNMODAL &lt;&gt; ACTION::Yes THENUnnecessary Separatorshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-separators/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-separators/There should be no unnecessary separators. +Bad code IF Customer.FINDFIRST THEN;; Good code -IF Customer.FINDFIRST THEN;Unnecessary TRUE/FALSEhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-truefalse/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-truefalse/Do not use TRUE or FALSE keywords unnecessarily if the expression is already an logical expression. Bad code +IF Customer.FINDFIRST THEN;Unnecessary TRUE/FALSEhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-truefalse/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/unnecessary-truefalse/Do not use TRUE or FALSE keywords unnecessarily if the expression is already an logical expression. +Bad code IF IsPositive() = TRUE THEN Good code IF IsPositive THEN Bad code -IF Complete &lt;\&gt; TRUE THEN Good code -IF NOT Complete THENVariable Already Scopedhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/variable-already-scoped/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/variable-already-scoped/Do not use scope &lsquo;&rsquo;.'' qualifier unnecessarily when a variable is already implicitly or explicitly scoped. It keeps the code simpler. Bad code -ReturnRcptHeader.SETRANGE(ReturnRcptHeader.&quot;Return Order No.&quot;,&quot;Document No.&quot;); Good code -ReturnRcptHeader.SETRANGE(&quot;Return Order No.&quot;,&quot;Document No.&quot;); Bad code +IF Complete &lt;&gt; TRUE THEN Good code +IF NOT Complete THENVariable Already Scopedhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/variable-already-scoped/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/variable-already-scoped/Do not use scope &lsquo;&rsquo;.'' qualifier unnecessarily when a variable is already implicitly or explicitly scoped. It keeps the code simpler. +Bad code +ReturnRcptHeader.SETRANGE(ReturnRcptHeader.&#34;Return Order No.&#34;,&#34;Document No.&#34;); Good code +ReturnRcptHeader.SETRANGE(&#34;Return Order No.&#34;,&#34;Document No.&#34;); Bad code WITH ChangeLogSetupTable DO BEGIN ... IF ChangeLogSetupTable.DELETE THEN ... END; Good code WITH ChangeLogSetupTable DO BEGIN ... IF DELETE THEN ... END;Variable Naminghttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/variable-naming/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/variable-naming/Variables that refer to a C/AL object must contain the objects name, abbreviated where necessary. A variable must begin with a capital letter. Blanks, periods, and other characters (such as parentheses) that would make quotation marks around a variable necessary must be omitted. -If a variable is a compound of two or more words or abbreviations, each word or abbreviation should begin with a capital letter. Bad code -... WIPBuffer@1002 : Record 1018 .Variables Declarations Orderhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/variables-declarations-order/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/variables-declarations-order/Variables declarations should be ordered by type. In general, object and complex variable types are listed first followed by simple variables. The order should be the same as the object list in the object designer for C/AL objects. Afterwards come the complex variables like RecordRef, .NET, FieldRef etc. At the end come all the simple data types in no particular order. Bad code -StartingDateFilter@1002 : Text\[30\]; Vend@1003 : Record 23; Good code \ No newline at end of file +If a variable is a compound of two or more words or abbreviations, each word or abbreviation should begin with a capital letter. +Bad code +... WIPBuffer@1002 : Record 1018 .Variables Declarations Orderhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/variables-declarations-order/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/readability/variables-declarations-order/Variables declarations should be ordered by type. In general, object and complex variable types are listed first followed by simple variables. The order should be the same as the object list in the object designer for C/AL objects. Afterwards come the complex variables like RecordRef, .NET, FieldRef etc. At the end come all the simple data types in no particular order. +Bad code +StartingDateFilter@1002 : Text[30]; Vend@1003 : Record 23; Good code \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/keyword-pairs-indentation/index.html b/navpatterns/3-cal-coding-guidelines/readability/keyword-pairs-indentation/index.html index 5350d77a..3a0288b5 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/keyword-pairs-indentation/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/keyword-pairs-indentation/index.html @@ -5,16 +5,16 @@ Keyword Pairs - Indentation :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -255,15 +255,15 @@ ALGuidelines.Dev

      Keyword Pairs - Indentation

      -

      The IF..THEN pair, WHILE..DO pair, and FOR..DO pair must appear on the same line or the same level of indentation. Bad code

      -
      IF (x = y) AND
      -(a = b) THEN
      -
      -

      Good code

      -
      IF (x = y) AND
      -(a = b) 
      -THEN
      -
      +

      The IF..THEN pair, WHILE..DO pair, and FOR..DO pair must appear on the same line or the same level of indentation.

      +

      Bad code

      +
      IF (x = y) AND
      +   (a = b) THEN
      +

      Good code

      +
      IF (x = y) AND
      +   (a = b) 
      +THEN
      +
      @@ -276,12 +276,12 @@ THEN
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/line-start-keywords/index.html b/navpatterns/3-cal-coding-guidelines/readability/line-start-keywords/index.html index 3af89730..dff6a4e3 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/line-start-keywords/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/line-start-keywords/index.html @@ -5,16 +5,16 @@ Line Start Keywords :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,21 +255,21 @@ ALGuidelines.Dev

      Line Start Keywords

      -

      The END, IF, REPEAT, FOR, WHILE, ELSE and CASE statement should always start a line. Bad code

      -
      IF IsContactName THEN ValidateContactName
      -ELSE IF IsSalespersonCode THEN ValidateSalespersonCode
      -ELSE IF IsSalesCycleCode THEN ValidatSalesCycleCode;
      -
      -

      Good code

      -
      IF IsContactName THEN
      -ValidateContactName
      -ELSE
      -IF IsSalespersonCode THEN
      -ValidateSalespersonCode
      -ELSE
      -IF IsSalesCycleCode THEN
      -ValidatSalesCycleCode;
      -
      +

      The END, IF, REPEAT, FOR, WHILE, ELSE and CASE statement should always start a line.

      +

      Bad code

      +
      IF IsContactName THEN ValidateContactName
      +  ELSE IF IsSalespersonCode THEN ValidateSalespersonCode
      +    ELSE IF IsSalesCycleCode THEN ValidatSalesCycleCode;
      +

      Good code

      +
      IF IsContactName THEN
      +  ValidateContactName
      +ELSE
      +  IF IsSalespersonCode THEN
      +    ValidateSalespersonCode
      +  ELSE
      +    IF IsSalesCycleCode THEN
      +      ValidatSalesCycleCode;
      +
      @@ -282,12 +282,12 @@ ValidatSalesCycleCode;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/lonely-repeat/index.html b/navpatterns/3-cal-coding-guidelines/readability/lonely-repeat/index.html index 66371380..22161960 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/lonely-repeat/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/lonely-repeat/index.html @@ -5,16 +5,16 @@ Lonely Repeat :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,14 +255,14 @@ ALGuidelines.Dev

      Lonely Repeat

      -

      The REPEAT statement should always be alone on a line. -Bad code

      -
      IF ReservEntry.FINDSET THEN REPEAT  
      -
      -

      Good code

      -
      IF ReservEntry.FINDSET THEN
      -REPEAT
      -
      +

      The REPEAT statement should always be alone on a line.

      +

      Bad code

      +
      IF ReservEntry.FINDSET THEN REPEAT
      + 
      +

      Good code

      +
      IF ReservEntry.FINDSET THEN
      +  REPEAT
      +
      @@ -275,12 +275,12 @@ REPEAT
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/named-invocations/index.html b/navpatterns/3-cal-coding-guidelines/readability/named-invocations/index.html index a8074587..1cc4c89c 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/named-invocations/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/named-invocations/index.html @@ -5,16 +5,16 @@ Named Invocations :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,13 +255,12 @@ ALGuidelines.Dev

      Named Invocations

      -

      When calling an object statically use the name, not the number -Bad code

      -
      PAGE.RUNMODAL(525,SalesShptLine)  
      -
      -

      Good code

      -
      PAGE.RUNMODAL(PAGE::"Posted Sales Shipment Lines",SalesShptLine)
      -
      +

      When calling an object statically use the name, not the number

      +

      Bad code

      +
      PAGE.RUNMODAL(525,SalesShptLine)  
      +

      Good code

      +
      PAGE.RUNMODAL(PAGE::"Posted Sales Shipment Lines",SalesShptLine)
      +
      @@ -274,12 +273,12 @@ Bad code

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/nested-withs/index.html b/navpatterns/3-cal-coding-guidelines/readability/nested-withs/index.html index 03afc226..86ec740e 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/nested-withs/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/nested-withs/index.html @@ -5,16 +5,16 @@ Nested WITHs :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,22 +255,21 @@ ALGuidelines.Dev

      Nested WITHs

      -

      Do not nest WITHs that reference different types of objects. -Bad code

      -
      WITH PostedWhseShptLine DO BEGIN
      -...
      -WITH ItemLedgEntry DO
      -InsertBufferRec(...,"Serial No.","Lot No.",...);
      -...
      -END;
      -
      -

      Good code

      -
      WITH PostedWhseShptLine DO BEGIN
      -...
      -InsertBufferRec(...,ItemLedgEntry."Serial No.",ItemLedgEntry."Lot No.",...);
      -...
      -END;
      -
      +

      Do not nest WITHs that reference different types of objects.

      +

      Bad code

      +
      WITH PostedWhseShptLine DO BEGIN
      +  ...
      +  WITH ItemLedgEntry DO
      +    InsertBufferRec(...,"Serial No.","Lot No.",...);
      +    ...
      +END;
      +

      Good code

      +
      WITH PostedWhseShptLine DO BEGIN
      +  ...
      +  InsertBufferRec(...,ItemLedgEntry."Serial No.",ItemLedgEntry."Lot No.",...);
      +  ...
      +END;
      +
      @@ -283,12 +282,12 @@ END;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/one-statement-per-line/index.html b/navpatterns/3-cal-coding-guidelines/readability/one-statement-per-line/index.html index 0405f8d4..5b35cc5d 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/one-statement-per-line/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/one-statement-per-line/index.html @@ -5,16 +5,16 @@ One Statement Per Line :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,21 +255,21 @@ ALGuidelines.Dev

      One Statement Per Line

      -

      A line of code should not have more than one statement. -Bad code

      -
      IF OppEntry.FIND('-') THEN EXIT  
      -
      -

      Good code

      -
      IF OppEntry.FIND('-') THEN   
      -EXIT  
      -
      +

      A line of code should not have more than one statement.

      Bad code

      -
      TotalCost += Cost; TotalAmt += Amt;  
      -
      -

      Good code

      -
      TotalCost += Cost; 
      -TotalAmt += Amt;
      -
      +
      IF OppEntry.FIND('-') THEN EXIT
      +
      +

      Good code

      +
      IF OppEntry.FIND('-') THEN   
      +  EXIT  
      +
      +

      Bad code

      +
      TotalCost += Cost; TotalAmt += Amt;
      +
      +

      Good code

      +
      TotalCost += Cost; 
      +TotalAmt += Amt;
      +
      @@ -282,12 +282,12 @@ TotalAmt += Amt;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/separate-if-and-else/index.html b/navpatterns/3-cal-coding-guidelines/readability/separate-if-and-else/index.html index 103f507d..0c37f8f8 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/separate-if-and-else/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/separate-if-and-else/index.html @@ -5,16 +5,16 @@ Separate IF and ELSE :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,18 +255,18 @@ ALGuidelines.Dev

      Separate IF and ELSE

      -

      IF and ELSE statements should be on separate lines. Bad code

      -
      IF Atom\[i+1\] = '\>' THEN HasLogicalOperator := TRUE ELSE BEGIN
      -...
      -END;
      -
      -

      Good code

      -
      IF Atom\[i+1\] = '\>' THEN
      -HasLogicalOperator := TRUE
      -ELSE BEGIN
      -...
      -END;
      -
      +

      IF and ELSE statements should be on separate lines.

      +

      Bad code

      +
      IF Atom[i+1] = '>' THEN HasLogicalOperator := TRUE ELSE BEGIN
      +    ...
      +END;
      +

      Good code

      +
      IF Atom[i+1] = '>' THEN
      +    HasLogicalOperator := TRUE
      +ELSE BEGIN
      +    ...
      +END;
      +
      @@ -279,12 +279,12 @@ END;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/spacing-binary-operators/index.html b/navpatterns/3-cal-coding-guidelines/readability/spacing-binary-operators/index.html index bdbf2ee4..d4200a0b 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/spacing-binary-operators/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/spacing-binary-operators/index.html @@ -5,16 +5,16 @@ Spacing Binary Operators :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,25 +255,20 @@ ALGuidelines.Dev

      Spacing Binary Operators

      -

      There must be exactly one space character on each side of a binary operator such as = + - AND OR =. The parameter comma operator however, should have no spaces. -Bad code

      -
      "Line Discount %" := "Line Discount Amount"/"Line Value"\*100  
      -
      -

      Good code

      -
      "Line Discount %" := "Line Discount Amount" / "Line Value" \* 100;  
      -
      +

      There must be exactly one space character on each side of a binary operator such as = + - AND OR =. The parameter comma operator however, should have no spaces.

      Bad code

      -
      StartDate := CALCDATE('<+'+FORMAT(Days + i)+'D\>', StartDate);  
      -
      -

      Good code

      -
      StartDate := CALCDATE('<+' + FORMAT(Days + i) + 'D\>',StartDate);  
      -
      -

      Bad code

      -
      StartDate := 0D; // Initialize  
      -
      -

      Good code

      -
      StartDate := 0D; // Initialize
      -
      +
      "Line Discount %" := "Line Discount Amount"/"Line Value"*100  
      +

      Good code

      +
      "Line Discount %" := "Line Discount Amount" / "Line Value" * 100; 
      +

      Bad code

      +
      StartDate := CALCDATE('<+'+FORMAT(Days + i)+'D>', StartDate);  
      +

      Good code

      +
      StartDate := CALCDATE('<+' + FORMAT(Days + i) + 'D>',StartDate);  
      +

      Bad code

      +
      StartDate := 0D;   // Initialize  
      +

      Good code

      +
      StartDate := 0D; // Initialize
      +
      @@ -286,12 +281,12 @@ Bad code

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/spacing-brackets-and/index.html b/navpatterns/3-cal-coding-guidelines/readability/spacing-brackets-and/index.html index 239da750..77e50c02 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/spacing-brackets-and/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/spacing-brackets-and/index.html @@ -5,16 +5,16 @@ Spacing Brackets and :: :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,25 +255,20 @@ ALGuidelines.Dev

      Spacing Brackets and ::

      -

      There must be no spaces characters before and after [] dimension brackets symbols or :: option symbols. -Bad code

      -
      A\[i\] \[j\] := Amt;  
      -
      -

      Good code

      -
      A\[i\]\[j\] := Amt;  
      -
      +

      There must be no spaces characters before and after [] dimension brackets symbols or :: option symbols.

      Bad code

      -
      "Currency Exchange Rate"."Fix Exchange Rate Amount" :: Currency:  
      -
      -

      Good code

      -
      "Currency Exchange Rate"."Fix Exchange Rate Amount"::Currency:  
      -
      -

      Bad code

      -
      IF FIND (Which) THEN  
      -
      -

      Good code

      -
      IF FIND(Which) THEN
      -
      +
      A[i] [j] := Amt;  
      +

      Good code

      +
      A[i][j] := Amt;  
      +

      Bad code

      +
      "Currency Exchange Rate"."Fix Exchange Rate Amount" :: Currency:  
      +

      Good code

      +
      "Currency Exchange Rate"."Fix Exchange Rate Amount"::Currency:  
      +

      Bad code

      +
      IF FIND (Which) THEN  
      +

      Good code

      +
      IF FIND(Which) THEN
      +
      @@ -286,12 +281,12 @@ Bad code

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/spacing-unary-operators/index.html b/navpatterns/3-cal-coding-guidelines/readability/spacing-unary-operators/index.html index e95ce028..25d62ba6 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/spacing-unary-operators/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/spacing-unary-operators/index.html @@ -5,16 +5,16 @@ Spacing Unary Operators :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,19 +255,16 @@ ALGuidelines.Dev

      Spacing Unary Operators

      -

      There must be no space between a unary operator and its argument (except for the NOT keyword). -Bad code

      -
      IF NOT(Type = Type::Item) THEN  
      -
      -

      Good code

      -
      IF NOT (Type = Type::Item) THEN  
      -
      +

      There must be no space between a unary operator and its argument (except for the NOT keyword).

      Bad code

      -
      DiscAmt := - "Discount Amount";  
      -
      -

      Good code

      -
      DiscAmt := -"Discount Amount";
      -
      +
      IF NOT(Type = Type::Item) THEN  
      +

      Good code

      +
      IF NOT (Type = Type::Item) THEN  
      +

      Bad code

      +
      DiscAmt := - "Discount Amount";  
      +

      Good code

      +
      DiscAmt := -"Discount Amount";
      +
      @@ -280,12 +277,12 @@ Bad code

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/suggested-abbreviations/index.html b/navpatterns/3-cal-coding-guidelines/readability/suggested-abbreviations/index.html index 21ec7fe8..3073d4c8 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/suggested-abbreviations/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/suggested-abbreviations/index.html @@ -5,16 +5,16 @@ Suggested Abbreviations :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -972,12 +972,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/temporary-variable-naming/index.html b/navpatterns/3-cal-coding-guidelines/readability/temporary-variable-naming/index.html index 6279cf4b..3790ab47 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/temporary-variable-naming/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/temporary-variable-naming/index.html @@ -5,16 +5,16 @@ Temporary Variable Naming :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -255,18 +255,16 @@ ALGuidelines.Dev

      Temporary Variable Naming

      -

      The name of a temporary variable must be prefixed with the word Temp and not otherwise. Bad code

      -
      JobWIPBuffer@1002 : TEMPORARY Record 1018;   
      -
      -

      Good code

      -
      TempJobWIPBuffer@1002 : TEMPORARY Record 1018;   
      -
      +

      The name of a temporary variable must be prefixed with the word Temp and not otherwise.

      Bad code

      -
      TempJobWIPBuffer@1002 : Record 1018;   
      -
      -

      Good code

      -
      CopyOfJobWIPBuffer@1002 : Record 1018;
      -
      +
      JobWIPBuffer@1002 : TEMPORARY Record 1018;   
      +

      Good code

      +
      TempJobWIPBuffer@1002 : TEMPORARY Record 1018;   
      +

      Bad code

      +
      TempJobWIPBuffer@1002 : Record 1018;   
      +

      Good code

      +
      CopyOfJobWIPBuffer@1002 : Record 1018;
      +
      @@ -279,12 +277,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/textconst-suffixes/index.html b/navpatterns/3-cal-coding-guidelines/readability/textconst-suffixes/index.html index 4c6a023a..926c07db 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/textconst-suffixes/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/textconst-suffixes/index.html @@ -5,16 +5,16 @@ TextConst Suffixes :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,81 +255,70 @@ ALGuidelines.Dev

      TextConst Suffixes

      -

      TextConst variable names should have a suffix (an approved three-letter suffix: Msg, Tok, Err, Qst, Lbl, Txt) describing usage. -Bad code

      -
      CannotDeleteLine@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
      -...
      -ERROR(CannotDeleteLine,TABLECAPTION);
      -
      -

      Good code

      -
      CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
      -...
      -ERROR(CannotDeleteLineErr,TABLECAPTION); 
      -
      +

      TextConst variable names should have a suffix (an approved three-letter suffix: Msg, Tok, Err, Qst, Lbl, Txt) describing usage.

      Bad code

      -
      Text000@1011 : TextConst 'ENU="has been changed (initial a %1: %2= %3, %4= %5)"';
      -...
      -SalesLine.FIELDERROR(Type,STRSUBSTNO(Text000,...);
      -...
      -
      -

      Good code

      -
      TypeHasBeenChangedErr@1011 : TextConst 'ENU="has been changed (initial a %1: %2= %3, %4= %5)"';
      -...
      -SalesLine.FIELDERROR(Type,STRSUBSTNO(TypeHasBeenChangedErr,...);
      -... 
      -
      -

      Bad code

      -
      Text004@1004 : TextConst 'ENU=Indenting the Job Tasks \#1\#\#\#\#\#\#\#\#\#\#.';
      -...
      -Window@1007 : Dialog;
      -...
      -Window.OPEN(Text004);
      -
      -

      Good code

      -
      IndentingMsg@1004 : TextConst 'ENU=Indenting the Job Tasks \#1\#\#\#\#\#\#\#\#\#\#.';
      -...
      -Window@1007 : Dialog;
      -...
      -Window.OPEN(IndentingMsg);
      -
      -

      Bad code

      -
      Text002@1005 : TextConst 'ENU=You cannot delete a %1 that is used in one or more setup windows.\\ Do you want to open the G/L Account No. Where-Used List Window?';
      -...
      -IF CONFIRM(Text002,TRUE,GLAcc.TABLECAPTION) THEN
      -
      -

      Good code

      -
      OpenWhereUsedWindowQst@1005 : TextConst 'ENU=You cannot delete a %1 that is used in one or more setup windows.\\ Do you want to open the G/L Account No. Where-Used List Window?';
      -...
      -IF CONFIRM(OpenWhereUsedWindowQst,TRUE,GLAcc.TABLECAPTION) THEN
      -
      -

      Bad code

      -
      Selection := STRMENU(Text003,2);
      -...
      -Text003@1002 : TextConst 'ENU=&Copy dimensions from BOM,&Retrieve dimensions from components';
      -
      -

      Good code

      -
      Selection := STRMENU(CopyFromQst,2);
      -...
      -CopyFromQst@1002 : TextConst 'ENU=&Copy dimensions from BOM,&Retrieve dimensions from components';
      -
      -

      Bad code

      -
      DATASET
      +
      CannotDeleteLine@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
      +...
      +ERROR(CannotDeleteLine,TABLECAPTION);
      +

      Good code

      +
      CannotDeleteLineErr@1005 : TextConst 'ENU=You cannot delete this line because one or more rating values exists.';
      +...
      +ERROR(CannotDeleteLineErr,TABLECAPTION); 
      +

      Bad code

      +
      Text000@1011 : TextConst 'ENU="has been changed (initial a %1: %2= %3, %4= %5)"';
      +...
      +SalesLine.FIELDERROR(Type,STRSUBSTNO(Text000,...);
      +...
      +

      Good code

      +
      TypeHasBeenChangedErr@1011 : TextConst 'ENU="has been changed (initial a %1: %2= %3, %4= %5)"';
      +...
      +SalesLine.FIELDERROR(Type,STRSUBSTNO(TypeHasBeenChangedErr,...);
      +... 
      +

      Bad code

      +
      Text004@1004 : TextConst 'ENU=Indenting the Job Tasks \#1\#\#\#\#\#\#\#\#\#\#.';
      +...
      +Window@1007 : Dialog;
      +...
      +    Window.OPEN(Text004);
      +

      Good code

      +
      IndentingMsg@1004 : TextConst 'ENU=Indenting the Job Tasks \#1\#\#\#\#\#\#\#\#\#\#.';
      +...
      +Window@1007 : Dialog;
      +...
      +    Window.OPEN(IndentingMsg);
      +

      Bad code

      +
      Text002@1005 : TextConst 'ENU=You cannot delete a %1 that is used in one or more setup windows.\\ Do you want to open the G/L Account No. Where-Used List Window?';
      +...
      +IF CONFIRM(Text002,TRUE,GLAcc.TABLECAPTION) THEN
      +

      Good code

      +
      OpenWhereUsedWindowQst@1005 : TextConst 'ENU=You cannot delete a %1 that is used in one or more setup windows.\\ Do you want to open the G/L Account No. Where-Used List Window?';
      +...
      +IF CONFIRM(OpenWhereUsedWindowQst,TRUE,GLAcc.TABLECAPTION) THEN
      +

      Bad code

      +
      Selection := STRMENU(Text003,2);
      +...
      +Text003@1002 : TextConst 'ENU=&Copy dimensions from BOM,&Retrieve dimensions from components';
      +

      Good code

      +
          Selection := STRMENU(CopyFromQst,2);
      +    ...
      +    CopyFromQst@1002 : TextConst 'ENU=&Copy dimensions from BOM,&Retrieve dimensions from components';
      +

      Bad code

      +
      DATASET
       {
      -...
      -{ 1 ;1 ;Column ;Chart\_of\_AccountsCaption;
      -SourceExpr=Chart\_of\_AccountsCaption }
      -...
      -Chart\_of\_AccountsCaption@9647 : TextConst 'ENU=Chart of Accounts';
      -
      -

      Good code

      -
      DATASET
      +...
      +    { 1 ;1 ;Column ;Chart_of_AccountsCaption;
      +                    SourceExpr=Chart_of_AccountsCaption }
      +...
      +Chart_of_AccountsCaption@9647 : TextConst 'ENU=Chart of Accounts';
      +

      Good code

      +
      DATASET
       {
      -...
      -{ 1 ;1 ;Column ;Chart\_of\_AccountsCaption;
      -SourceExpr=ChartOfAccountsLbl }
      -...
      -ChartOfAccountsLbl@9647 : TextConst 'ENU=Chart of Accounts';
      -
      +... + { 1 ;1 ;Column ;Chart_of_AccountsCaption; + SourceExpr=ChartOfAccountsLbl } +... +ChartOfAccountsLbl@9647 : TextConst 'ENU=Chart of Accounts'; +
      @@ -342,12 +331,12 @@ ChartOfAccountsLbl@9647 : TextConst 'ENU=Chart of Accounts';
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/unary-operator-line-end/index.html b/navpatterns/3-cal-coding-guidelines/readability/unary-operator-line-end/index.html index 921aa252..1b8792b5 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/unary-operator-line-end/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/unary-operator-line-end/index.html @@ -5,16 +5,16 @@ Unary Operator Line End :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,15 +255,14 @@ ALGuidelines.Dev

      Unary Operator Line End

      -

      Do not end a line with unary operator. -Bad code

      -
      "Quantity Handled (Base)" := -
      -"Quantity Handled (Base)");
      -
      -

      Good code

      -
      "Quantity Handled (Base)" :=
      -- "Quantity Handled (Base)");
      -
      +

      Do not end a line with unary operator.

      +

      Bad code

      +
      "Quantity Handled (Base)" := -
      +    "Quantity Handled (Base)");
      +

      Good code

      +
      "Quantity Handled (Base)" :=
      +    - "Quantity Handled (Base)");
      +
      @@ -276,12 +275,12 @@ Bad code

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/unnecessary-compound-parenthesis/index.html b/navpatterns/3-cal-coding-guidelines/readability/unnecessary-compound-parenthesis/index.html index b4ff5ee6..d106bd06 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/unnecessary-compound-parenthesis/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/unnecessary-compound-parenthesis/index.html @@ -5,16 +5,16 @@ Unnecessary Compound Parenthesis :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -247,15 +247,7 @@ ALGuidelines.Dev
      - +
      @@ -263,20 +255,16 @@ ALGuidelines.Dev

      Unnecessary Compound Parenthesis

      -

      Use parenthesis only to enclose compound expressions inside compound expressions. -Bad code

      -
      IF ("Costing Method" = "Costing Method"::Standard) THEN  
      -
      -

      Good code

      -
      IF "Costing Method" = "Costing Method"::Standard THEN  
      -
      -

      +

      Use parenthesis only to enclose compound expressions inside compound expressions.

      Bad code

      -
      ProfitPct = -(Profit) / CostAmt \* 100;  
      -
      -

      Good code

      -
      ProfitPct = -Profit / CostAmt \* 100;
      -
      +
      IF ("Costing Method" = "Costing Method"::Standard) THEN  
      +

      Good code

      +
      IF "Costing Method" = "Costing Method"::Standard THEN  
      +

      Bad code

      +
      ProfitPct = -(Profit) / CostAmt * 100;  
      +

      Good code

      +
      ProfitPct = -Profit / CostAmt * 100;
      +
      @@ -289,12 +277,12 @@ Bad code

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/unnecessary-else/index.html b/navpatterns/3-cal-coding-guidelines/readability/unnecessary-else/index.html index 97e8a503..900fb955 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/unnecessary-else/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/unnecessary-else/index.html @@ -5,16 +5,16 @@ Unnecessary ELSE :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,17 +255,17 @@ ALGuidelines.Dev

      Unnecessary ELSE

      -

      ELSE should not be used when the last action in the THEN part is an EXIT, BREAK, SKIP, QUIT, ERROR. Bad code

      -
      IF IsAdjmtBinCodeChanged THEN
      -ERROR(AdjmtBinCodeChangeNotAllowedErr,...)
      -ELSE
      -ERROR(BinCodeChangeNotAllowedErr,...);
      -
      -

      Good code

      -
      IF IsAdjmtBinCodeChanged THEN
      -ERROR(AdjmtBinCodeChangeNotAllowedErr,...)
      -ERROR(BinCodeChangeNotAllowedErr,...);
      -
      +

      ELSE should not be used when the last action in the THEN part is an EXIT, BREAK, SKIP, QUIT, ERROR.

      +

      Bad code

      +
      IF IsAdjmtBinCodeChanged THEN
      +    ERROR(AdjmtBinCodeChangeNotAllowedErr,...)
      +ELSE
      +    ERROR(BinCodeChangeNotAllowedErr,...);
      +

      Good code

      +
      IF IsAdjmtBinCodeChanged THEN
      +    ERROR(AdjmtBinCodeChangeNotAllowedErr,...)
      +ERROR(BinCodeChangeNotAllowedErr,...);
      +
      @@ -278,12 +278,12 @@ ERROR(BinCodeChangeNotAllowedErr,...);
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/unnecessary-function-parenthesis/index.html b/navpatterns/3-cal-coding-guidelines/readability/unnecessary-function-parenthesis/index.html index 87abe28a..ab5353aa 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/unnecessary-function-parenthesis/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/unnecessary-function-parenthesis/index.html @@ -5,16 +5,16 @@ Unnecessary Function Parenthesis :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,19 +255,16 @@ ALGuidelines.Dev

      Unnecessary Function Parenthesis

      -

      Do not use parenthesis in a function call if the function does not have any parameters. -Bad code

      -
      IF ReservMgt.IsPositive() THEN  
      -
      -

      Good code

      -
      IF ReservMgt.IsPositive THEN  
      -
      +

      Do not use parenthesis in a function call if the function does not have any parameters.

      Bad code

      -
      IF ChangeStatusForm.RUNMODAL() <\> ACTION::Yes THEN  
      -
      -

      Good code

      -
      IF ChangeStatusForm.RUNMODAL <\> ACTION::Yes THEN
      -
      +
      IF ReservMgt.IsPositive() THEN  
      +

      Good code

      +
      IF ReservMgt.IsPositive THEN  
      +

      Bad code

      +
      IF ChangeStatusForm.RUNMODAL() <> ACTION::Yes THEN  
      +

      Good code

      +
      IF ChangeStatusForm.RUNMODAL <> ACTION::Yes THEN
      +
      @@ -280,12 +277,12 @@ Bad code

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/unnecessary-separators/index.html b/navpatterns/3-cal-coding-guidelines/readability/unnecessary-separators/index.html index 9589ccba..74ddfd08 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/unnecessary-separators/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/unnecessary-separators/index.html @@ -5,16 +5,16 @@ Unnecessary Separators :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,12 +255,12 @@ ALGuidelines.Dev

      Unnecessary Separators

      -

      There should be no unnecessary separators. Bad code

      -
      IF Customer.FINDFIRST THEN;;  
      -
      -

      Good code

      -
      IF Customer.FINDFIRST THEN;
      -
      +

      There should be no unnecessary separators.

      +

      Bad code

      +
      IF Customer.FINDFIRST THEN;;  
      +

      Good code

      +
      IF Customer.FINDFIRST THEN;
      +
      @@ -273,12 +273,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/unnecessary-truefalse/index.html b/navpatterns/3-cal-coding-guidelines/readability/unnecessary-truefalse/index.html index 8631077a..9ec793e3 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/unnecessary-truefalse/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/unnecessary-truefalse/index.html @@ -5,16 +5,16 @@ Unnecessary TRUE/FALSE :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,19 +255,16 @@ ALGuidelines.Dev

      Unnecessary TRUE/FALSE

      -

      Do not use TRUE or FALSE keywords unnecessarily if the expression is already an logical expression. -Bad code

      -
      IF IsPositive() = TRUE THEN  
      -
      -

      Good code

      -
      IF IsPositive THEN  
      -
      +

      Do not use TRUE or FALSE keywords unnecessarily if the expression is already an logical expression.

      Bad code

      -
      IF Complete <\> TRUE THEN  
      -
      -

      Good code

      -
      IF NOT Complete THEN
      -
      +
      IF IsPositive() = TRUE THEN  
      +

      Good code

      +
      IF IsPositive THEN  
      +

      Bad code

      +
      IF Complete <> TRUE THEN  
      +

      Good code

      +
      IF NOT Complete THEN
      +
      @@ -280,12 +277,12 @@ Bad code

      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/variable-already-scoped/index.html b/navpatterns/3-cal-coding-guidelines/readability/variable-already-scoped/index.html index 55498181..3d618452 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/variable-already-scoped/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/variable-already-scoped/index.html @@ -5,16 +5,16 @@ Variable Already Scoped :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,27 +255,24 @@ ALGuidelines.Dev

      Variable Already Scoped

      -

      Do not use scope ‘’.'' qualifier unnecessarily when a variable is already implicitly or explicitly scoped. It keeps the code simpler. -Bad code

      -
      ReturnRcptHeader.SETRANGE(ReturnRcptHeader."Return Order No.","Document No.");  
      -
      -

      Good code

      -
      ReturnRcptHeader.SETRANGE("Return Order No.","Document No.");  
      -
      +

      Do not use scope ‘’.'' qualifier unnecessarily when a variable is already implicitly or explicitly scoped. It keeps the code simpler.

      Bad code

      -
      WITH ChangeLogSetupTable DO BEGIN
      -...
      -IF ChangeLogSetupTable.DELETE THEN
      -...
      -END;
      -
      -

      Good code

      -
      WITH ChangeLogSetupTable DO BEGIN
      -...
      -IF DELETE THEN
      -...
      -END;
      -
      +
      ReturnRcptHeader.SETRANGE(ReturnRcptHeader."Return Order No.","Document No.");  
      +

      Good code

      +
      ReturnRcptHeader.SETRANGE("Return Order No.","Document No.");  
      +

      Bad code

      +
      WITH ChangeLogSetupTable DO BEGIN
      +    ...
      +    IF ChangeLogSetupTable.DELETE THEN
      +        ...
      +END;
      +

      Good code

      +
      WITH ChangeLogSetupTable DO BEGIN
      +    ...
      +    IF DELETE THEN
      +        ...
      +END;
      +
      @@ -288,12 +285,12 @@ END;
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/variable-naming/index.html b/navpatterns/3-cal-coding-guidelines/readability/variable-naming/index.html index 324b58a9..4b4c7cf0 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/variable-naming/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/variable-naming/index.html @@ -5,16 +5,16 @@ Variable Naming :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -258,47 +258,42 @@ ALGuidelines.Dev

        Variables that refer to a C/AL object must contain the objects name, abbreviated where necessary.

        A variable must begin with a capital letter.

        Blanks, periods, and other characters (such as parentheses) that would make quotation marks around a variable necessary must be omitted.

        -

        If a variable is a compound of two or more words or abbreviations, each word or abbreviation should begin with a capital letter. -Bad code

        -
        ...
        -WIPBuffer@1002 : Record 1018
        -...
        -OBJECT Table Job WIP Buffer
        -
        -

        Good code

        -
        ...
        -JobWIPBuffer@1002 : Record 1018
        -...
        -OBJECT Table Job WIP Buffer 
        -
        +

        If a variable is a compound of two or more words or abbreviations, each word or abbreviation should begin with a capital letter.

        Bad code

        -
        ...
        -Postline@1004 : Codeunit 12;
        -...
        -OBJECT Codeunit Gen. Jnl.-Post Line
        -
        -

        Good code

        -
        ...
        -GenJnlPostLine@1004 : Codeunit 12;
        -...
        -OBJECT Codeunit Gen. Jnl.-Post Line 
        -
        -

        Bad code

        -
        LOCAL PROCEDURE HandleCustDebitCredit@17(...;"Amount (LCY)"@1001 : Decimal;...);
        -BEGIN
        -IF ((... ("Amount (LCY)" \> 0)) ...) OR
        -((... ("Amount (LCY)" < 0)) ...)
        -THEN BEGIN
        -...
        -
        -

        Good code

        -
        LOCAL PROCEDURE HandleCustDebitCredit@17(...;AmountLCY@1001 : Decimal;...);
        -BEGIN
        -IF ((... (AmountLCY \> 0)) ...) OR
        -((... (AmountLCY < 0)) ...)
        -THEN BEGIN
        -...
        -
        +
        ...
        +    WIPBuffer@1002 : Record 1018
        +...
        +OBJECT Table Job WIP Buffer
        +

        Good code

        +
        ...
        +    JobWIPBuffer@1002 : Record 1018
        +...
        +OBJECT Table Job WIP Buffer 
        +

        Bad code

        +
        ...
        +    Postline@1004 : Codeunit 12;
        +...
        +OBJECT Codeunit Gen. Jnl.-Post Line
        +

        Good code

        +
        ...
        +    GenJnlPostLine@1004 : Codeunit 12;
        +...
        +OBJECT Codeunit Gen. Jnl.-Post Line 
        +

        Bad code

        +
        LOCAL PROCEDURE HandleCustDebitCredit@17(...;"Amount (LCY)"@1001 : Decimal;...);
        +BEGIN
        +    IF ((... ("Amount (LCY)" \> 0)) ...) OR
        +        ((... ("Amount (LCY)" < 0)) ...)
        +    THEN BEGIN
        +    ...
        +

        Good code

        +
        LOCAL PROCEDURE HandleCustDebitCredit@17(...;AmountLCY@1001 : Decimal;...);
        +BEGIN
        +    IF ((... (AmountLCY \> 0)) ...) OR
        +        ((... (AmountLCY < 0)) ...)
        +    THEN BEGIN
        +    ...
        +
      @@ -311,12 +306,12 @@ THEN BEGIN
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/readability/variables-declarations-order/index.html b/navpatterns/3-cal-coding-guidelines/readability/variables-declarations-order/index.html index 61959fa7..46819165 100644 --- a/navpatterns/3-cal-coding-guidelines/readability/variables-declarations-order/index.html +++ b/navpatterns/3-cal-coding-guidelines/readability/variables-declarations-order/index.html @@ -5,16 +5,16 @@ Variables Declarations Order :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,14 +255,14 @@ ALGuidelines.Dev

      Variables Declarations Order

      -

      Variables declarations should be ordered by type. In general, object and complex variable types are listed first followed by simple variables. The order should be the same as the object list in the object designer for C/AL objects. Afterwards come the complex variables like RecordRef, .NET, FieldRef etc. At the end come all the simple data types in no particular order. Bad code

      -
      StartingDateFilter@1002 : Text\[30\];
      -Vend@1003 : Record 23;
      -
      -

      Good code

      -
      Vend@1003 : Record 23;
      -StartingDateFilter@1002 : Text\[30\];
      -
      +

      Variables declarations should be ordered by type. In general, object and complex variable types are listed first followed by simple variables. The order should be the same as the object list in the object designer for C/AL objects. Afterwards come the complex variables like RecordRef, .NET, FieldRef etc. At the end come all the simple data types in no particular order.

      +

      Bad code

      +
      StartingDateFilter@1002 : Text[30];
      +Vend@1003 : Record 23;
      +

      Good code

      +
      Vend@1003 : Record 23;
      +StartingDateFilter@1002 : Text[30];
      +
      @@ -275,12 +275,12 @@ StartingDateFilter@1002 : Text\[30\];
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/ux/actions-images/index.html b/navpatterns/3-cal-coding-guidelines/ux/actions-images/index.html index 6f92b77a..5615630d 100644 --- a/navpatterns/3-cal-coding-guidelines/ux/actions-images/index.html +++ b/navpatterns/3-cal-coding-guidelines/ux/actions-images/index.html @@ -5,16 +5,16 @@ Actions - Images :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,18 +255,17 @@ ALGuidelines.Dev

      Actions - Images

      -

      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 }
      -
      +

      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 }
      +
      @@ -279,12 +278,12 @@ Image=Report }
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/ux/confirm/index.html b/navpatterns/3-cal-coding-guidelines/ux/confirm/index.html index ab92ea66..c93617a9 100644 --- a/navpatterns/3-cal-coding-guidelines/ux/confirm/index.html +++ b/navpatterns/3-cal-coding-guidelines/ux/confirm/index.html @@ -5,16 +5,16 @@ CONFIRM :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,17 +255,16 @@ ALGuidelines.Dev

      CONFIRM

      -

      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
      -
      +

      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
      +
      @@ -278,12 +277,12 @@ IF CONFIRM(ChangeAllOpenedEntriesQst,TRUE) THEN
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/ux/fielderror/index.html b/navpatterns/3-cal-coding-guidelines/ux/fielderror/index.html index 4e891ca0..54ea3b83 100644 --- a/navpatterns/3-cal-coding-guidelines/ux/fielderror/index.html +++ b/navpatterns/3-cal-coding-guidelines/ux/fielderror/index.html @@ -5,16 +5,16 @@ FIELDERROR :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -255,17 +255,16 @@ ALGuidelines.Dev

      FIELDERROR

      -

      Never use FIELDERROR with a period as it is automatically inserted. -Bad code

      -
      InvalidValue@1025 : TextConst 'ENU=is invalid.';
      -...
      -Cust.FIELDERROR("No.",InvalidValue);
      -
      -

      Good code

      -
      InvalidValue@1025 : TextConst 'ENU=is invalid';
      -...
      -Cust.FIELDERROR("No.",InvalidValue);
      -
      +

      Never use FIELDERROR with a period as it is automatically inserted.

      +

      Bad code

      +
      InvalidValue@1025 : TextConst 'ENU=is invalid.';
      +...
      +Cust.FIELDERROR("No.",InvalidValue);
      +

      Good code

      +
      InvalidValue@1025 : TextConst 'ENU=is invalid';
      +...
      +Cust.FIELDERROR("No.",InvalidValue);
      +
      @@ -278,12 +277,12 @@ Cust.FIELDERROR("No.",InvalidValue);
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/ux/index.html b/navpatterns/3-cal-coding-guidelines/ux/index.html index 4eda93ca..d2477df1 100644 --- a/navpatterns/3-cal-coding-guidelines/ux/index.html +++ b/navpatterns/3-cal-coding-guidelines/ux/index.html @@ -6,16 +6,16 @@ UX :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev - - - + + +
        @@ -275,12 +275,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/ux/index.xml b/navpatterns/3-cal-coding-guidelines/ux/index.xml index 4227a37d..7014b1f7 100644 --- a/navpatterns/3-cal-coding-guidelines/ux/index.xml +++ b/navpatterns/3-cal-coding-guidelines/ux/index.xml @@ -1,9 +1,13 @@ -UX on AL Guidelineshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/Recent content in UX on AL GuidelinesHugo -- gohugo.ioen-usActions - Imageshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/actions-images/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/actions-images/All actions must have an image assigned to them. Bad code +UX on AL Guidelineshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/Recent content in UX on AL GuidelinesHugo -- gohugo.ioen-usActions - Imageshttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/actions-images/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/actions-images/All actions must have an image assigned to them. +Bad code { 7 ;1 ;Action ; CaptionML=ENU=Customer - &amp;Balance; RunObject=Report 121 } Good code -{ 7 ;1 ;Action ; CaptionML=ENU=Customer - &amp;Balance; RunObject=Report 121 } Image=Report }CONFIRMhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/confirm/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/confirm/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) THENFIELDERRORhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/fielderror/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/fielderror/Never use FIELDERROR with a period as it is automatically inserted. Bad code -InvalidValue@1025 : TextConst 'ENU=is invalid.'; ... Cust.FIELDERROR(&quot;No.&quot;,InvalidValue); Good code -InvalidValue@1025 : TextConst 'ENU=is invalid'; ... Cust.FIELDERROR(&quot;No.&quot;,InvalidValue);MESSAGE and ERRORhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/message-and-error/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/message-and-error/Always end MESSAGE or ERROR with a period. Bad code -CustIsBlockedErr@1025 : TextConst 'ENU=You cannot %1 this type of document when Customer %2 is blocked with type %3'; ... ERROR(CustIsBlockedErr,...); Good code -CustIsBlockedErr@1025 : TextConst 'ENU=You cannot %1 this type of document when Customer %2 is blocked with type %3.'; ... ERROR(CustIsBlockedErr,...); \ No newline at end of file +{ 7 ;1 ;Action ; CaptionML=ENU=Customer - &amp;Balance; RunObject=Report 121 } Image=Report }CONFIRMhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/confirm/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/confirm/Always end CONFIRM with a question mark. +Bad code +ChangeAllOpenedEntriesQst@1000 : TextConst &#39;ENU=Do you want to change all open entries for every customer and vendor that are not blocked&#39;; ... IF CONFIRM(ChangeAllOpenedEntriesQst,TRUE) THEN Good code +ChangeAllOpenedEntriesQst@1000 : TextConst &#39;ENU=Do you want to change all open entries for every customer and vendor that are not blocked?&#39;; ... IF CONFIRM(ChangeAllOpenedEntriesQst,TRUE) THENFIELDERRORhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/fielderror/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/fielderror/Never use FIELDERROR with a period as it is automatically inserted. +Bad code +InvalidValue@1025 : TextConst &#39;ENU=is invalid.&#39;; ... Cust.FIELDERROR(&#34;No.&#34;,InvalidValue); Good code +InvalidValue@1025 : TextConst &#39;ENU=is invalid&#39;; ... Cust.FIELDERROR(&#34;No.&#34;,InvalidValue);MESSAGE and ERRORhttps://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/message-and-error/Mon, 01 Jan 0001 00:00:00 +0000https://alguidelines.dev/navpatterns/3-cal-coding-guidelines/ux/message-and-error/Always end MESSAGE or ERROR with a period. +Bad code +CustIsBlockedErr@1025 : TextConst &#39;ENU=You cannot %1 this type of document when Customer %2 is blocked with type %3&#39;; ... ERROR(CustIsBlockedErr,...); Good code +CustIsBlockedErr@1025 : TextConst &#39;ENU=You cannot %1 this type of document when Customer %2 is blocked with type %3.&#39;; ... ERROR(CustIsBlockedErr,...); \ No newline at end of file diff --git a/navpatterns/3-cal-coding-guidelines/ux/message-and-error/index.html b/navpatterns/3-cal-coding-guidelines/ux/message-and-error/index.html index fd8c7e54..26ffb73e 100644 --- a/navpatterns/3-cal-coding-guidelines/ux/message-and-error/index.html +++ b/navpatterns/3-cal-coding-guidelines/ux/message-and-error/index.html @@ -5,16 +5,16 @@ MESSAGE and ERROR :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -255,17 +255,16 @@ ALGuidelines.Dev

      MESSAGE and ERROR

      -

      Always end MESSAGE or ERROR with a period. -Bad code

      -
      CustIsBlockedErr@1025 : TextConst 'ENU=You cannot %1 this type of document when Customer %2 is blocked with type %3';
      -...
      -ERROR(CustIsBlockedErr,...);
      -
      -

      Good code

      -
      CustIsBlockedErr@1025 : TextConst 'ENU=You cannot %1 this type of document when Customer %2 is blocked with type %3.';
      -...
      -ERROR(CustIsBlockedErr,...);
      -
      +

      Always end MESSAGE or ERROR with a period.

      +

      Bad code

      +
      CustIsBlockedErr@1025 : TextConst 'ENU=You cannot %1 this type of document when Customer %2 is blocked with type %3';
      +...
      +ERROR(CustIsBlockedErr,...);
      +

      Good code

      +
      CustIsBlockedErr@1025 : TextConst 'ENU=You cannot %1 this type of document when Customer %2 is blocked with type %3.';
      +...
      +ERROR(CustIsBlockedErr,...);
      +
      @@ -278,12 +277,12 @@ ERROR(CustIsBlockedErr,...);
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/4-get-involved/code-of-conduct/index.html b/navpatterns/4-get-involved/code-of-conduct/index.html index f46a9717..de07f375 100644 --- a/navpatterns/4-get-involved/code-of-conduct/index.html +++ b/navpatterns/4-get-involved/code-of-conduct/index.html @@ -5,16 +5,16 @@ Code of Conduct :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -275,12 +275,12 @@ ALGuidelines.Dev
        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/4-get-involved/index.html b/navpatterns/4-get-involved/index.html index da422a4d..ed261e0f 100644 --- a/navpatterns/4-get-involved/index.html +++ b/navpatterns/4-get-involved/index.html @@ -6,16 +6,16 @@ (OLD) Get Involved :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
      - - - + + +
        @@ -250,15 +250,15 @@ ALGuidelines.Dev

        Reminder, this is an ARCHIVE of the Patterns site, this information is not current.

        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, Setup Table and definitely Hooks which will be a great investment in reducing your upgrade time. The more experienced developers can read directly the newer patterns, like Surrogate Key, Easy Update, Totals on Subpages, Using Queries instead of nested loops etc.

        +

        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, Setup Table and definitely Hooks which will be a great investment in reducing your upgrade time. The more experienced developers can read directly the newer patterns, like Surrogate Key, Easy Update, Totals on Subpages, Using Queries instead of nested loops 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 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 that we’ve used.

        +

        Once you have the idea, writing it down shouldn’t take long. You will be helped by adopting the template 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, 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.

        +

        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.

      @@ -272,12 +272,12 @@ ALGuidelines.Dev
      - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/4-get-involved/patterns-authors/index.html b/navpatterns/4-get-involved/patterns-authors/index.html index b9fad909..14f4dc50 100644 --- a/navpatterns/4-get-involved/patterns-authors/index.html +++ b/navpatterns/4-get-involved/patterns-authors/index.html @@ -5,16 +5,16 @@ Patterns Authors :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
        @@ -254,7 +254,7 @@ ALGuidelines.Dev

      Patterns Authors

      -

      This is the list of people that have been part of the NAV Design Patterns team. If you would like to join the project follow the instructions provided on Be a NAV Pattern Author page.

      +

      This is the list of people that have been part of the NAV Design Patterns team. If you would like to join the project follow the instructions provided on Be a NAV Pattern Author page.

      Project administrator: Bogdana Botez.

      Authors (in alphabetical order):

        @@ -560,12 +560,12 @@ _ (2 patterns)

        - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/4-get-involved/template-for-writing-nav-design-patterns/index.html b/navpatterns/4-get-involved/template-for-writing-nav-design-patterns/index.html index 8410896a..862d9daf 100644 --- a/navpatterns/4-get-involved/template-for-writing-nav-design-patterns/index.html +++ b/navpatterns/4-get-involved/template-for-writing-nav-design-patterns/index.html @@ -5,16 +5,16 @@ Template for writing Nav Design Patterns :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev - - - + + +
          @@ -304,12 +304,12 @@ ALGuidelines.Dev
          - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/index.html b/navpatterns/index.html index 30ebb45f..7c8109df 100644 --- a/navpatterns/index.html +++ b/navpatterns/index.html @@ -6,16 +6,16 @@ NAV Patterns Archive :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
        - - - + + +
          @@ -270,12 +270,12 @@ ALGuidelines.Dev
          - - - - - - - + + + + + + + \ No newline at end of file diff --git a/navpatterns/related-links/index.html b/navpatterns/related-links/index.html index f951be8f..19470808 100644 --- a/navpatterns/related-links/index.html +++ b/navpatterns/related-links/index.html @@ -5,16 +5,16 @@ Related Links :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -32,9 +32,9 @@ ALGuidelines.Dev
        - - - + + +
          @@ -270,12 +270,12 @@ ALGuidelines.Dev
          - - - - - - - + + + + + + + \ No newline at end of file diff --git a/tags/index.html b/tags/index.html index 1d100a79..c48dced1 100644 --- a/tags/index.html +++ b/tags/index.html @@ -6,16 +6,16 @@ Tags :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
        - - - + + +
          @@ -259,12 +259,12 @@ ALGuidelines.Dev
          - - - - - - - + + + + + + + \ No newline at end of file diff --git a/welcome/index.html b/welcome/index.html index 0f995e25..38e92cbb 100644 --- a/welcome/index.html +++ b/welcome/index.html @@ -6,16 +6,16 @@ Welcome :: AL Guidelines - - - - - - - - - - + + + + + + + + + + @@ -33,9 +33,9 @@ ALGuidelines.Dev
        - - - + + +
          @@ -263,12 +263,12 @@ ALGuidelines.Dev
          - - - - - - - + + + + + + + \ No newline at end of file