Development guidelines

Basic guidelines

  • To have only one model per implementation project. This will save you from possible issues with model compatibility and dependencies in between.

  • To have only one extension per application object. This will allow the developer to see and understand all customizations of the object.

  • Do not have several developers on same one-box. Otherwise, you may face issues with source code controlling in Visual Studio.

  • When a developer changes someone’s code, or relies on it in own code, then, in any case, he takes responsibility for the correct execution of the code.

  • No basic/standard functionality can be limited with new customization/extension.

  • Each extension/customization should be enframed with condition for checking if the new feature is enabled.

  • If it is foreseen to have more than one custom model, each condition checking (using if or swith/case) should be implemented as separate method that could be extended with Chain-Of-Commands in another model.

    Example (incorrect):

    class SalesFormLetterInvoice_PRJ_Extension
    {
        public void printJournal()
        {
            ...
            if (salesParmUpdate.PrintAgreement_PRJ)
            {
                ... // do additional printing of agreement with invoice printing
            }
        }
    }
    

    Example (correct):

    class SalesFormLetterInvoice_PRJ_Extension
    {
        protected boolean checkPrintAgreement_PRJ()
        {
            return salesParmUpdate.PrintAgreement_PRJ;
        }
      
        public void printJournal()
        {
            ...
            if (this.checkPrintAgreement_PRJ())
            {
                ... // do additional printing of agreement with invoice printing
            }
        }
    }
    
  • Added code should be compact (optimized with its size) and implement required business logic with maximum use of existing standard application objects/methods.

  • Each customization should be checked with another developer for BP. See Code review

  • To run something in batch and if it will not be consumed externally (with web-service calls etc.), it is simplier to implement it using the RunBaseBatch framework rather than SysOperations.

Records creating

  • For the creation of InventJournalTable/InventJournalTrans records InventJournalTableData/InventJournalTransData framework should be used.

  • For the creation of LedgerJournalTable/LedgerJournalTrans and related records use LedgerJournalEngine framework. See Classes\createLedgerJournalTrans()

  • Any other records should be created with same way, as user creates it manually:

    • initialize record with calling common.initValue() method

    • concenquenced filling of the required fields with mandatory executing commong.validateField() and common.modifiedField() method. Consequence of field filling should be described in Functional Specification.

    • executing of common.validateWrite() and common.write() methods

    • all validation errors should be processed properly.

    Example:

    ...
    protected void createPurchTable()
    {
        PurchTable purchTable;
          
          
    }
      
    ...
    
    • If there is data entity for table which could be used for record inserting, it should be used as preferrable solution.

Reintenting the wheel / Red line crossing

Sometimes Developers implement solutions that should be reworked from scratch after development is finished. To prevent this, following solutions should signal to Technical Architect to do additional task review.

  • Using container-type fields in tables.

  • Storing list of values (separated with comma, etc.) in a single field/variable.

  • Using any multi-value parameters (like container, List, Set, etc.) in RunBase-framework classes.

  • Addressing to financial dimension by name

Application objects guidelines

Tables

Macros

Financial dimensions

  • No ‘Custom dimensions’ allowed for implementation project.
    Each financial dimension should be implemented with own table (called backing entity) and separate setup form.

  • Do not address to financial dimension (dimension attribute) by name (with hardcoding its name).

    The possible solution is:

    • Add base enum Dimension attribute type with values starting from 0 (None) and continue with required values.

      Example:

      0 - None
      1 - Department
      2 - Cost center
      3 - Purpose

    • Extend financial dimensions (DimensionAttribute table) with Dimension attribute type field.

    • Set Dimension attribute type for each existing financial dimension in the Financial dimension form.

    • Use base enum Dimension attribute type to:

      • find needed dimension attribute in the code by type
      • use in table relations for limiting dimension attribute values.

Comments

  • As history of object changes is stored in source control system (repository), thus there is no strict rule to mark added code with additional comments (containing Work Item Id, Work Item name, Date and Developer alias etc.).

  • It is prohibited to leave commented/not-executed code as well as previous version of the code.

  • Developer could add comments which clarify code execution.

Code

  • Use global (class-level) variables ONLY when it is strongly needed. Well-formed code has no need to use global variables except of interacting with another objects (via mutators-methods).

    Example (incorrect):

    class TutorialGlobalVariables
    {
        SalesTable salesTable;
      
        protected void processSalesTable()
        {
           salesTable.DeliveryDate = datenull();
           salesTable.update();
        }
      
        void run()
        {
            ttsBegin;
            while select forupdate salesTable
            {
                this.processSalesTable();
            }
            ttsCommit;
        }
      
    }
    

    Example (correct):

    class TutorialGlobalVariables
    {
        protected void processSalesTable(SalesTable _salesTable)
        {
           _salesTable.DeliveryDate = datenull();
           _salesTable.update();
        }
      
        void run()
        {
            SalesTable salesTable;
              
            ttsBegin;
            while select forupdate salesTable
            {
                this.processSalesTable(salesTable);
            }
            ttsCommit;
        }
    }
    
  • It is not necessary to have additional variable to ogranize parameter or master setup record caching. Trust to table-wise or record-wise caching on server side (CacheLookup=Found,EntireTable etc.).

    Example:

    class TutorialSkippingVariables
    {
        void run()
        {
            SalesTable salesTable;
              
            ttsBegin;
            while select forupdate salesTable
            {
                if (conlen(salesTable.totalWeightAndVolume()))
                {
                    salesTable.Reservation = SalesParameters::find().Reservation;
                    salesTable.update();
                }
            }
            ttsCommit;
        }
    }
    
  • Avoid to use custom enums in conditions (if, switch, etc.) to have algorithm branching. Use business-centric terms instead. Compare two following example.

    Example (incorrect with hard-coded business logic):

    class TutorialEnumBranching
    {
        void run()
        {
            ...
              
            if (salesLine.inventTable().MainProductType_PRJ == MainProductType_PRJ::Item)
            {
                // do reservation
            }
        }
    }
    

    Example (with flexible setup on custom setup table):

    class TutorialEnumBranching
    {
        void run()
        {
            ...
              
            if (MainProductType_PRJ::find(salesLine.inventTable().MainProductType_PRJ).ItemReservation == ItemReservation::Automatic)
            {
                // do reservation
            }
        }
    }
    
  • Use positive logic for condition checking and for extended fields.
    Use Jobs to update existing data and initValue() method for proper initialization of field values. Compare two following examples.

    Example (incorrect):

    Tables\SalesTable.Extension_PRJ\Fields\SkipPackingSlip
      
    class SalesFormLetter_PackingSlip
    {
        void run()
        {
            if (salesTable.SkipPackingSlip)
            {
                return;
            }
            ...
            // do packing slip processing
        }
    }
    

    Example (correct):

    Tables\SalesTable.Extension_PRJ\Fields\ProcessPackingSlip
      
    class SalesFormLetter_PackingSlip
    {
        void run()
        {
            if (! salesTable.ProcessPackingSlip)
            {
                return;
            }
            ...
            // do packing slip processing
        }
    }
    

UI guidelines

Filtering and sorting

  • By default, each field on the form should be available for filtering and sorting (if there is no explicit requrement to make it using display- or edit-methods)

  • Unavailability of filtering/sorting for added field should be approved with Consultant.

Column widths

  • For any table/grid column should be able to change the width

Fields moving

  • For all fields in the tabular part of the form, the operation of moving to another place (changing the order of columns) should be available.

Size/form position

  • No operations that are directly related to changing the size and position of the forms should not change the size and position of the form.

  • Form controls that allow their sizes to be changed must scale when the sizes of the forms change.

Viewing details

  • All fields with Foreigh Keys must allow to View details (go to main table).

Confirmation of irreversible actions

  • Before performing all irreversible actions, it is required to ask the user for confirmation in the dialog.
    By default, the dialog should focus on the button that does not lead to irreversible actions.

Interaction with user

  • No basic UI functionality (filtering, sorting, exporting to Excel etc.) can be limited with new customization/extension.

  • Any function of the should lead to some result. The result can be an any message of change in the interface (for example, opening a form).

  • It is forbidden to have any interaction with user, requires his answer, within the transaction.

  • You should not use a chain of dialogs from several windows to confirm the action. If it is necessary to branch the algorithm, the choice of the branch of the algorithm should be indicated in the initial dialog box.

  • Any using of notexists join should be approved by Technical Architect.

  • Creation of new temporary tables (either InMemory or TempDB) or using persistent table as temporary (using .setTmp() clause) should be approved with Technical Architect.

Integration guidelines

  • Any integration flow (external interface) should use only external codes for each mapped table of Dynamics 365.

Example:

1. Get value '0001200' as ExtProductId from CSV-file or web-request
2. Get ExtCodeId from interface setup
3. Use ExtProductId and ExtCodeId to find external code value (in ExtCodeValueTable table) related with product (EcoResProduct table) record.
4. Use founded product record in further logic, otherwise display error: Product with external value '%ExtProductId%' for external code '%ExtCodeId%' not found.
  • Any integration flow should execute the only logic, that can be reproduced by the user manually.

  • No inbound interface (integration flow) can auto-create setup or masterdata to its proper processing.
    All setup must be done before running the interface that could rely on proper setup maded.

  • Using standard or existing frameworks (Data Management etc.) is the main priority.