Dynamics 365 Tag - Page 4 of 16 - - Page 4

Tag Archives: Dynamics 365

Restoring a Deleted Posted Bank Reconciliation in Business Central: A Comprehensive Guide

Are you having trouble restoring a deleted posted bank reconciliation in Microsoft Dynamics 365 Business Central? In this blog, I’m going to guide you through the process of effectively restoring a deleted posted bank reconciliation and ensuring the accuracy of your financial records. You’ll learn the step-by-step procedure to re-post a deleted bank reconciliation, along with best practices to prevent future errors and maintain the integrity of your financial data. Let’s get started! Steps to Achieve Goal: page 50100 BankLedgerEntryEditable {     ApplicationArea = All;     Caption = ‘Bank Ledger Entry Editable’;     PageType = List;     SourceTable = “Bank Account Ledger Entry”;     UsageCategory = Lists;     Permissions = tabledata “Bank Account Ledger Entry” = RIMD;     layout     {         area(Content)         {             repeater(General)             {                 field(“Document No.”; Rec.”Document No.”)                 {                     ToolTip = ‘Specifies the document number on the bank account entry.’;                 }                 field(“Statement No.”; Rec.”Statement No.”)                 {                     ToolTip = ‘Specifies the bank account statement that the ledger entry has been applied to, if the Statement Status is Bank Account Ledger Applied.’;                 }                 field(“Statement Line No.”; Rec.”Statement Line No.”)                 {                     ToolTip = ‘Specifies the number of the statement line that has been applied to by this ledger entry line.’;                 }                 field(“Statement Status”; Rec.”Statement Status”)                 {                     ToolTip = ‘Specifies the statement status of the bank account ledger entry.’;                 }                 field(Amount; Rec.Amount)                 {                     ToolTip = ‘Specifies the amount of the entry denominated in the applicable foreign currency.’;                 }                 field(“Amount (LCY)”; Rec.”Amount (LCY)”)                 {                     ToolTip = ‘Specifies the amount of the entry in LCY.’;                 }                 field(“Posting Date”; Rec.”Posting Date”)                 {                     ToolTip = ‘Specifies the posting date for the entry.’;                 }             }         }     } } pageextension 50101 PostedBankAccRecon extends “Bank Account Statement List” {     trigger OnDeleteRecord(): Boolean     begin         Error(‘You cannot delete a bank account reconciliation entry.’);     end; } To conclude, by following these steps, you can successfully undo a deleted posted bank reconciliation in Business Central. The process involves editing the Bank Ledger Entry, recreating the bank reconciliation with the same details, and ensuring the previously deleted reconciliation is removed through AL. With this approach, you maintain accurate financial records and ensure that your bank account reconciliation process runs smoothly without any discrepancies. If you need further assistance or have specific questions about your Business Central setup, feel free to reach out for personalized guidance.  Happy Reconciliation! We hope you found this blog useful, and if you would like to discuss anything, you can reach out to us at transform@cloudfonts.com.

Seamless Integration: How to Sync Business Central with External Systems Instantly or in Batches

In today’s fast-paced business world, integrating your ERP system (like Business Central) with other external systems is crucial for streamlining processes and ensuring data consistency. However, if you’re new to API integrations or struggling with how to send data from Business Central to another system, don’t worry! In this post, I am going to walk you through the process of sending data from Business Central to an external system using APIs. By the end of this guide, you’ll have a clear understanding of how to perform integrations smoothly, without the complexity. Steps to Achieve Goal: You can easily send data from Business Central to an external system by calling the link set in General Ledger Setup. Below is the logic for sending data via an API. You can encapsulate this logic inside a Codeunit, and call it as needed based on your synchronization requirements: Real-Time Data Sync: If you want the data to be synced in real-time (for example, as soon as new data is entered into Business Central), you can call the procedure within the OnAfterInsert() trigger. This ensures that every time a new record is created, it automatically triggers the procedure to send the data. trigger OnAfterInsert() begin     SendPostRequest(Rec); end; Batch Data Sync: If you prefer to sync the data at the end of a batch process (for example, at the end of the day), you can loop through the records using FindSet() and then call the procedure inside the loop. This will send data in bulk at a scheduled time rather than in real-time. procedure SyncDataInBatch() var     Rec_SO: Record “Sales Header”; begin    Rec_SO.setrange(CreatedAt,today()); // Apply any filter as per your need.     if Rec_SO.FindSet() then         repeat             SendPostRequest(Rec_SO);         until Rec_SO.Next() = 0; end; // Below is the logic for posting data from BC to Third Party Applications via API   procedure SendPostRequest(var Rec_SO: Record “Sales Header”)     var         HttpClient: HttpClient;         HttpContent: HttpContent;         HttpResponseMessage: HttpResponseMessage;         HttpRequestMessage: HttpRequestMessage;         JsonObject: JsonObject;         JsonText: Text;         Rec_GLE: Record “General Ledger Setup”;         contentHeaders: HttpHeaders;         OutPutString: Text;     begin           Rec_GLE.Get();         Rec_GLE.TestField(“API Link”); // where the other system API link has been stored and we are using via AL         HttpRequestMessage.SetRequestUri(Rec_GLE.”API Link”);         HttpRequestMessage.Method := ‘POST’;         JsonObject.Add(‘system_id’, Rec_SO.SystemId); // Passing Sales Header System ID(GUID)         JsonObject.Add(‘document_number’, Rec_SO.”No.”); // Passing Sales Header No         JsonObject.Add(‘type’, ‘ReleasedSalesInvoice’); // Passing Sales Header type         JsonObject.WriteTo(JsonText);         HttpContent.WriteFrom(JsonText);           HttpContent.GetHeaders(contentHeaders);         contentHeaders.Add(‘charset’, ‘UTF-8’);         contentHeaders.Remove(‘Content-Type’);         contentHeaders.Add(‘Content-Type’, ‘application/json; charset=utf-8’);           HttpRequestMessage.Content(HttpContent);         if HttpClient.Send(HttpRequestMessage, HttpResponseMessage) then             if HttpResponseMessage.IsSuccessStatusCode then begin                 HttpResponseMessage.Content.ReadAs(OutPutString);                 Message(‘%1’, OutPutString);             end             else                 Error(‘Error %1’, HttpResponseMessage.ReasonPhrase);     end; Conclusion: To conclude, sending data between Business Central and other systems is not as complicated as it might seem. By following the steps outlined above, you’ll be able to create smooth, efficient integrations that will save time, reduce errors, and improve your business processes. Happy Coding! We hope you found this blog useful, and if you would like to discuss anything, you can reach out to us at transform@cloudfonts.com.

Business Central Translations: Working with XLIFF Files – Part 2

By the end of this guide, you will be able to generate, edit, and implement XLIFF files in Microsoft Dynamics 365 Business Central to support multiple languages seamlessly. This will enable your application to display translated content for UI labels, reports, and messages, ensuring a smooth experience for users across different regions. Why does this matter? Using XLIFF files, businesses can easily localize Business Central applications without modifying source code, making multilingual support efficient and scalable. By leveraging tools like NAB AL Tools, translations can be managed effortlessly, enhancing global usability. Generating an XLIFF File To illustrate the XLIFF process, let’s go through an example where we add a custom field named “Custom_Field” on the Customer Card page. Step 1: Creating a Custom Field First, we create a new field, Custom_Field, on the Customer Card page using AL code: Step 2: Enabling Translation File Feature In the app.json file, ensure that the TranslationFile feature is enabled: Step 3: Building the Project Now, build the extension using the shortcut CTRL + Shift + B. This will generate an .xlf file automatically in the translation folder. By default, the file is generated in English (en-US). Using NAB Extension for Translation To simplify translation tasks, you can install the NAB AL Tools extension from the Visual Studio Code marketplace. This extension helps in managing translation files efficiently by allowing automated translation suggestions and quick file updates. Steps to Use NAB AL Tools: A) Install NAB AL Tools. B) Press CTRL + Shift + P and select NAB: Create translation XLF for new language. C) Enter the language code (e.g., fr-FR for French – France). D) Choose Yes when prompted to match translations from BaseApp. E) A new fr-FR.xlf file will be generated. Translating the XLIFF File To translate the XLIFF file into another language (e.g., French), follow these steps: Example: Translating Report Labels In Business Central RDLC reports, hardcoded text values can also be translated using labels. Instead of writing static text, define labels within the AL code: Using FieldCaption ensures that report labels dynamically adapt to the selected language, improving localization without manual modifications. When switching languages, the label automatically retrieves the corresponding translation from the XLIFF file. Modifying Standard Fields Standard field names in Business Central can also be modified using the BaseApplication.g.xlf file. You can find this file in public repositories, such as: BaseApplication.g.xlf Modifying this file allows changes to default field captions and system messages in multiple languages. Insert value in different languages via AL In Microsoft Dynamics 365 Business Central, AL enables efficient multilingual data handling. The image above illustrates a Customer Card where the “Nom” (Name) field contains the value “testing.” The AL code extends the Customer table, adding a custom field and an onInsert trigger to validate the Name field with “testing.” This ensures data consistency across different language settings. By leveraging AL, developers can automate multilingual field values, enhancing Business Central’s global usability. To conclude, managing translations in Business Central using XLIFF files enables businesses to support multiple languages efficiently. By generating XLIFF files, modifying them for translation, and leveraging tools like NAB AL Tools, businesses can ensure accurate and seamless localization. For further refinements, modifying report labels and system fields enhances multilingual support, improving the user experience across global deployments. We hope you found this blog useful, and if you would like to discuss anything, you can reach out to us at transform@cloudfonts.com.

Business Central Translations: Language Setup and Customization – Part 1

In today’s globalised world, firms frequently operate in numerous areas and languages. To efficiently manage worldwide operations, software with multilingual capabilities is required. Microsoft Dynamics 365 Business Central (BC) includes a powerful translation system that enables enterprises to customise language choices, thereby increasing user experience and operational efficiencies. This article looks at how translations function in Business Central and how they may be used to support global business operations. Why Are Translations Important in Business Central? Businesses that expand into new areas face a variety of languages, currencies, and regulatory regimes. Ensuring that employees can interact with Business Central in their native language improves the software’s usability and productivity. Business Central allows users to configure numerous languages across various modules, allowing workers to work smoothly in their favourite language. It also allows translations for custom fields, reports, and data entry, assuring consistency and correctness in both internal and external interactions. How Translation Works in Business Central Business Central supports several languages, including English, French, German, and Spanish. Here’s an outline on how to activate and use translations successfully. 1. Configuring Language Settings The first step in enabling multilingual support is to configure the Language Settings in Business Central. Users can choose their favourite language or use the organization’s default language settings. This guarantees that when a user logs in, the interface, menus, and forms appear in their preferred language. To configure a language in Business Central: 2. Standard Text Translations Business Central provides built-in translations for standard interface elements and commonly used terms such as “Sales Orders,” “Invoices,” and “Purchase Orders.” These translations are included in the base application by Microsoft. For example, changing the language from English to French automatically updates the captions. However, some standard texts may not be translated by default. To install additional language support: Once installed, the system updates with the new language settings, ensuring a localized user experience. 3. Translating Custom Fields Many businesses customize Business Central by adding custom fields, tables, and industry-specific terminology. While these enhancements improve operational efficiency, they may not be automatically translated in the base system. To resolve this, Business Central provides the CaptionML property, which allows developers to define multilingual captions for custom elements. Example: English: Displays field names and labels in English. French: The same fields are shown with French translations. By implementing the CaptionML property, businesses ensure a seamless multilingual experience even for customized elements. To conclude, Microsoft Dynamics 365 Business Central makes it simple for multinational companies to handle multilingual customers. Companies can improve usability and efficiency across regions by changing language settings, adding extra translations, and ensuring that custom fields are translated using CaptionML. Embracing Business Central’s translation skills enables firms to operate efficiently in a global market while providing a consistent and localized experience to all users. We hope you found this blog useful, and if you would like to discuss anything, you can reach out to us at transform@cloudfonts.com.

A Comprehensive Guide to Backups and Restores in Dynamics 365 Business Central

Managing your organization’s data effectively is a critical task for ensuring business continuity. Dynamics 365 Business Central simplifies this process by offering built-in backup and restore features via the Business Central Admin Center. In this blog, we’ll explore how you can utilize these features to safeguard your environment and handle potential mishaps. Overview of Backups in Dynamics 365 Business Central Business Central automatically manages backups for your production and sandbox environments. These backups are stored securely for a limited period, allowing administrators to restore environments when needed. The retention period and capabilities for restoring backups are influenced by the environment type (production or sandbox). Key Features of the Backup System Restoring an Environment Restoring an environment is a straightforward process, ensuring minimal downtime. Follow these steps to restore an environment using the Admin Center: Step 1: Access the Admin Center Log in to the Business Central Admin Center using your administrator account. Step 2: Select the Environment Navigate to the Environments page and select the environment you wish to restore. Step 3: Initiate the Restore Process Click on the Restore button and choose a backup point from the available restore options. Step 4: Configure Restore Options Step 5: Confirm the Action Review the details and confirm the restore. The system will notify you once the process is complete. To encapsulate, the backup and restore functionalities in Dynamics 365 Business Central offer a reliable safety net for your organization’s data. By leveraging the Admin Center, administrators can easily safeguard business continuity and minimize risks associated with data loss or corruption. We hope you found this blog useful, and if you would like to discuss anything, you can reach out to us at transform@cloudfonts.com.

Get Started with Reservation Hierarchies in Dynamics 365 Finance & Operations

Managing inventory in a systematic way is essential for any business. Dynamics 365 Finance & Operations (D365F&O) provides reservation hierarchies to streamline how inventory is reserved and tracked across dimensions like site, warehouse, batch, or serial number. This guide explains the steps to enable reservation hierarchies and demonstrates their usage with practical examples. Reservation hierarchies are tools that determine the order in which inventory dimensions are used to allocate stock. For example, you might first reserve items by site and warehouse, followed by batch and serial numbers. This process helps ensure that inventory is allocated in a logical and efficient manner. Creating Reservation Hierarchies 2. Define the Hierarchy: 3. Select Dimensions: 4. Save and Finalize: Assigning Reservation Hierarchies to Products After creating the hierarchy, assign it to products to activate its functionality: Repeat these steps for all applicable products to standardize the process. Assigning Reservation Hierarchies to Products After creating the hierarchy, assign it to products to activate its functionality: Repeat these steps for all applicable products to standardize the process. Using Reservation Hierarchies in Transactions Sales Orders: When processing a sales order, the system automatically reserves inventory based on the hierarchy. It allocates stock step-by-step through the defined dimensions. Production Orders: For production, reservation hierarchies ensure materials are reserved systematically, avoiding stock conflicts. Transfer Orders: While transferring stock, the hierarchy helps select inventory from the correct dimensions, improving accuracy. Benefits of Reservation Hierarchies To conclude, reservation hierarchies are a simple yet powerful feature in D365F&O. They allow businesses to control how inventory is reserved, ensuring accuracy and efficiency in every transaction. By configuring them properly, you can streamline your operations and reduce errors. Take the time to test these features in a sandbox environment before using them in your live setup. This will help you understand how they work and ensure they fit your business needs. With reservation hierarchies in place, managing inventory becomes easier and more reliable, helping your business stay organized and efficient. That’s it for this blog! Hope this helps! Keep Sharing!! We hope you found this blog useful, and if you would like to discuss anything, you can reach out to us at transform@cloudfonts.com.

How to Display the ‘+New’ Quote Button Only for System Administrators Using JavaScript and Ribbon Workbench in Dynamics 365 CRM

Uploading and managing quotes efficiently is crucial for Dynamics 365 CRM users. However, sometimes you may want to restrict certain buttons, such as the ‘+New’ Quote button, to only users with specific roles, like the “System Administrator.” In this guide, I’ll walk you through how to achieve this by leveraging JavaScript and the Ribbon Workbench tool in Dynamics 365. This method allows administrators to control button visibility based on user roles, ensuring that only users with the correct permissions can access sensitive functionality. The Use-Case: Restricting Access to the ‘+New’ Quote Button for Non-Administrators. Imagine a scenario where your organization needs to ensure that only users with a “System Administrator” role can create new quotes in Dynamics 365. This is crucial for maintaining control over who can initiate important processes within your CRM system. Using JavaScript and Ribbon Workbench, you can easily customize the UI to hide the ‘+New’ Quote button for non-administrators. Here’s how this use case can be implemented: In this scenario, your team wants to ensure that only system administrators have access to the “+New” button for creating quotes in the system. For non-administrators, the button will be hidden from both the homepage subgrid and the main quote tab to prevent unauthorized users from creating quotes. By using the Ribbon Workbench tool, a custom JavaScript function is created to check if a user has the “System Administrator” role. If they do, the “+New” button remains visible, and they can create a new quote. For all other users, the button is hidden. Key Components of the Solution 1. Ribbon Workbench: The Ribbon Workbench tool allows you to customize the Dynamics 365 ribbon, enabling you to create custom buttons and define their visibility and actions. It is used to create the new custom “+New” Quote button, which replaces the default button while maintaining system integrity. 2. JavaScript Customization:  Custom JavaScript is used to manage role-based access for the “+New” Quote button. The script checks the user’s role within Dynamics 365 to ensure that only users with the “System Administrator” role can view and use the button. This helps enforce security and restricts unauthorized users from creating new quotes. 3. Enable Rule for Button Visibility:   An Enable Rule is set to control the visibility of the custom “+New” Quote button based on the user’s role. It ensures that only users with the “System Administrator” role can see and use the button, while hiding it for other users. 4. Custom Button Action (Command): The command linked to the custom “+New” button triggers a custom action (JavaScript function) to open the quote form. This ensures that the action associated with the button aligns with the business needs and provides a seamless user experience for administrators. Step-by-Step Process Sign in to Dynamics 365 using your URL, such as abc.dynamics.com, and enter your credentials or login to make.powerapps.com Create a solution and add the web resource. Once it’s done login to ribbon workbench from XRM toolbox and connect to your organization. After logging in, it is recommended to create a new solution for Ribbon Workbench in Dynamics 365. Ensure that no forms, views, charts, or other entities are included, as Ribbon Workbench may fail to upload the solution with excessive data. Only include the Quote entity with no additional dependencies. Ensure the existing +New Quote button is hidden, as modifying Microsoft-standard buttons is not recommended. Instead, create a new custom button and implement the functionality for creating a new quote Form using custom JavaScript. I have provided the code for this functionality as well. Ensure that the existing +New button for quote would be hidden from the homepage Subgrid and the quotes main tab. Next step would be to create a enable rule. Enable rule is used to control the visibility and availability of a button or command of the button. Name the id of your choice but make sure to add the suffix Enable Rule. Here, un-customised is set to False. By setting isCore (or Un customized) to false, you’re indicating that the button or element is a custom component, not part of the out-of-the-box (core) solution provided by Microsoft. This helps differentiate custom actions from the default ones in the system. Below is the code for the new quote form create and user role-based code. Make sure to select the Function name properly. After setting the enable rule, go to the Commands section in Ribbon Workbench and rename the command. A command defines the action triggered by a button click. Since this is a new button, you’ll need to add the custom form opening code. Below is the function for creating the form. Final Steps: Once the command is added, don’t forget to add the Enable rule that you have created above. Once the command is added, make sure to add all the rules we wrote into the custom button. The image also needs to be added so that the icon can be visible. My custom +New icon looks like this. Testing: Once everything is done, make sure Publish the changes. You can now try to log in from the user that has no System administrator role. Once logged in, you can see that button is not visible. Button will be only visible to user that have system Administrator role. User having no System Administrator role. You can see below that there’s no +New button displayed. To conclude, by following this guide, you can efficiently control the visibility of the ‘+New’ Quote button in Dynamics 365 CRM, making it accessible only to users with the “System Administrator” role. This ensures better control over who can create quotes in the system while maintaining the flexibility of user roles. We hope you found this blog useful, and if you would like to discuss anything, you can reach out to us at transform@cloudfonts.com.

Enhancing the Recurring General Journal with Automated Approval Workflows in Dynamics 365 Business Central

In any accounting or financial system, ensuring that the proper approvals are in place is critical for maintaining audit trails and accountability. Microsoft Dynamics 365 Business Central (BC) offers powerful approval workflows that can automate this process, ensuring compliance and accuracy in your financial entries. This blog will guide you through creating an extension for the Recurring General Journal page, enabling automated approval requests for journal batches and journal lines. Understanding the Recurring General Journal The Recurring General Journal in Business Central allows users to post transactions that occur periodically, such as monthly expenses. However, posting journal entries often requires an approval process to guarantee compliance and accuracy. Our extension will empower users to request approvals, cancel requests, and manage the approval status directly from the Recurring General Journal. Solution Outline This extension will: Code Here’s the complete PageExtension code that extends the Recurring General Journal page: pageextension 50103 RecurringGenJnl extends “Recurring General Journal” {     layout     {         addafter(CurrentJnlBatchName)         {             field(StatusLine; Rec.StatusBatch)             {                 ApplicationArea = All;                 Caption = ‘Approval Status’;                 Editable = false;                 Visible = true;             }         }     }     actions     {         addafter(“F&unctions”)         {             group(“Request Approval”)             {                 Caption = ‘Request Approval’;                 group(SendApprovalRequest)                 {                     Caption = ‘Send Approval Request’;                     Image = SendApprovalRequest;                     action(SendApprovalRequestJournalLine)                     {                         ApplicationArea = Basic, Suite;                         Caption = ‘Journal Batch’;                         Image = SendApprovalRequest;                         Enabled = true;                         ToolTip = ‘Send all journal lines for approval, also those that you may not see because of filters.’;                         trigger OnAction()                         var                             GenJournalLine: Record “Gen. Journal Line”;                             GenJournalBatch: Record “Gen. Journal Batch”;                             ApprovalsMgmt: Codeunit “Approvals Mgmt.”;                             IsLineValid: Boolean;                         begin                             GenJournalBatch.Get(Rec.”Journal Template Name”, Rec.”Journal Batch Name”);                             if GenJournalLine.SetCurrentKey(“Journal Template Name”, “Journal Batch Name”) then begin                                 GenJournalLine.SetRange(“Journal Template Name”, GenJournalBatch.”Journal Template Name”);                                 GenJournalLine.SetRange(“Journal Batch Name”, GenJournalBatch.”Name”);                                 if GenJournalLine.FindSet() then                                     ApprovalsMgmt.TrySendJournalBatchApprovalRequest(GenJournalLine);                                 SetControlAppearanceFromBatch();                                 SetControlAppearance();                             end;                         end;                     }                 }                 group(CancelApprovalRequest)                 {                     Caption = ‘Cancel Approval Request’;                     Image = Cancel;                     Enabled = true;                     action(CancelApprovalRequestJournalBatch)                     {                         ApplicationArea = Basic, Suite;                         Caption = ‘Journal Batch’;                         Image = CancelApprovalRequest;                         ToolTip = ‘Cancel sending all journal lines for approval, also those that you may not see because of filters.’;                         trigger OnAction()                         var                             ApprovalsMgmt: Codeunit “Approvals Mgmt.”;                             GenJournalBatch: Record “Gen. Journal Batch”;                             GenJournalLine: Record “Gen. Journal Line”;                             IsLineValid: Boolean;                         begin                             GenJournalBatch.Get(Rec.”Journal Template Name”, Rec.”Journal Batch Name”);                             //ApprovalsMgmt.TryCancelJournalBatchApprovalRequest(GenJournalLine);                             if GenJournalLine.SetCurrentKey(“Journal Template Name”, “Journal Batch Name”) then begin                                 GenJournalLine.SetRange(“Journal Template Name”, GenJournalBatch.”Journal Template Name”);                                 GenJournalLine.SetRange(“Journal Batch Name”, GenJournalBatch.”Name”);                                 if GenJournalLine.FindSet() then                                     IsLineValid := true;                                 if (GenJournalLine.Status.ToUpper() = ‘OPEN’) or                                    (GenJournalLine.Status.ToUpper() = ‘APPROVED’) then begin                                     IsLineValid := false;                                 end;                                 if IsLineValid then                                     ApprovalsMgmt.TryCancelJournalBatchApprovalRequest(GenJournalLine);                                 //ApprovalsMgmt.TryCancelJournalLineApprovalRequests(GenJournalLine);                             end else begin                                 Message(‘Gen. Journal Batch not found for the provided template and batch names.’);                             end;                             SetControlAppearanceFromBatch();                             SetControlAppearance();                         end;                     }                 }                 group(Approval)                 {                     Caption = ‘Approval’;                     action(Approve)                     {                         ApplicationArea = All;                         Caption = ‘Approve’;                         Image = Approve;                         ToolTip = ‘Approve the requested changes.’;                         Visible = true;                         trigger OnAction()                         var                             ApprovalsMgmt: Codeunit “Approvals Mgmt.”;                         begin                             ApprovalsMgmt.ApproveGenJournalLineRequest(Rec);                         end;                     }                     action(Reject)                     {                         ApplicationArea = All;                         Caption = ‘Reject’;                         Image = Reject;                         ToolTip = ‘Reject the approval request.’;                         Visible = true;                         trigger OnAction()                         var                             ApprovalsMgmt: Codeunit “Approvals Mgmt.”;                         begin                             ApprovalsMgmt.RejectGenJournalLineRequest(Rec);                         end;                     }                     action(Delegate)                     {                         ApplicationArea = All;                         Caption = ‘Delegate’;                         Image = Delegate;                         ToolTip = ‘Delegate the approval to a substitute approver.’;                         Visible = OpenApprovalEntriesExistForCurrUser;                         trigger OnAction()                         var                             ApprovalsMgmt: Codeunit “Approvals Mgmt.”;                         begin                             ApprovalsMgmt.DelegateGenJournalLineRequest(Rec);                         end;                     }                 }             }         }     }     trigger OnOpenPage()     begin         SetControlAppearanceFromBatch();     end;     trigger OnAfterGetCurrRecord()     var         GenJournalBatch: Record “Gen. Journal Batch”;     begin         EnableApplyEntriesAction();         SetControlAppearance();         if GenJournalBatch.Get(GetJournalTemplateNameFromFilter(), CurrentJnlBatchName) then             SetApprovalStateForBatch(GenJournalBatch, Rec, OpenApprovalEntriesExistForCurrUser, OpenApprovalEntriesOnJnlBatchExist, OpenApprovalEntriesOnBatchOrAnyJnlLineExist, CanCancelApprovalForJnlBatch, CanRequestFlowApprovalForBatch, CanCancelFlowApprovalForBatch, CanRequestFlowApprovalForBatchAndAllLines, ApprovalEntriesExistSentByCurrentUser, EnabledGenJnlBatchWorkflowsExist, EnabledGenJnlLineWorkflowsExist);         ApprovalMgmt.GetGenJnlBatchApprovalStatus(Rec, Rec.StatusBatch, EnabledGenJnlBatchWorkflowsExist);     end;     trigger OnAfterGetRecord()     var         GenJournalLine: Record “Gen. Journal Line”;         GenJournalBatch: Record “Gen. Journal Batch”;         ApprovalsMgmt: Codeunit “Approvals Mgmt.”;     begin         GenJnlManagement.GetAccounts(Rec, AccName, BalAccName);         Rec.ShowShortcutDimCode(ShortcutDimCode);         SetUserInteractions();         GenJournalBatch.Get(Rec.”Journal Template Name”, Rec.”Journal Batch Name”);         if GenJournalLine.SetCurrentKey(“Journal Template Name”, “Journal Batch Name”) then begin             GenJournalLine.SetRange(“Journal Template Name”, GenJournalBatch.”Journal Template Name”);             GenJournalLine.SetRange(“Journal Batch Name”, GenJournalBatch.”Name”);             if GenJournalLine.FindSet() then                 repeat                     ApprovalMgmt.GetGenJnlLineApprovalStatus(Rec, Rec.StatusBatch, EnabledGenJnlLineWorkflowsExist);                 until GenJournalLine.Next() = 0;         end;     end;     trigger OnModifyRecord(): Boolean     var         GenJournalBatch: Record “Gen. Journal Batch”;         GenJournalLine: Record “Gen. Journal Line”;         ApprovalsMgmt: Codeunit “Approvals Mgmt.”;         IsLineValid: Boolean;     begin         SetUserInteractions();         ApprovalMgmt.CleanGenJournalApprovalStatus(Rec, Rec.StatusBatch, Rec.Status);         if Rec.StatusBatch = ‘Pending Approval’ then             Error(‘Modification not allowed. The journal batch “%1” has entries with a status of “Pending Approval”. Please approve, reject, or cancel these entries before making changes.’, GenJournalBatch.”Name”);     end;     var         GenJnlManagement: Codeunit GenJnlManagement;         JournalErrorsMgt: Codeunit “Journal Errors Mgt.”;         BackgroundErrorHandlingMgt: Codeunit “Background Error Handling Mgt.”;         ApprovalMgmt: Codeunit “Approvals Mgmt.”;         ChangeExchangeRate: Page “Change Exchange Rate”;         GLReconcile: Page Reconciliation;         GenJnlBatchApprovalStatus: Text[20];         GenJnlLineApprovalStatus: Text[20];         Balance: Decimal;         TotalBalance: Decimal;         NumberOfRecords: Integer;         ShowBalance: Boolean;         ShowTotalBalance: Boolean;         HasIncomingDocument: Boolean;         BalanceVisible: Boolean;         TotalBalanceVisible: Boolean;         StyleTxt: Text;         ApprovalEntriesExistSentByCurrentUser: Boolean;         OpenApprovalEntriesExistForCurrUser: Boolean;         OpenApprovalEntriesOnJnlBatchExist: Boolean;         OpenApprovalEntriesOnJnlLineExist: Boolean;         OpenApprovalEntriesOnBatchOrCurrJnlLineExist: Boolean;         OpenApprovalEntriesOnBatchOrAnyJnlLineExist: Boolean;         EnabledGenJnlLineWorkflowsExist: Boolean;         EnabledGenJnlBatchWorkflowsExist: Boolean;         ShowWorkflowStatusOnBatch: Boolean;         ShowWorkflowStatusOnLine: Boolean;         CanCancelApprovalForJnlBatch: Boolean;         CanCancelApprovalForJnlLine: Boolean;         ImportPayrollTransactionsAvailable: Boolean;         CanRequestFlowApprovalForBatch: Boolean;         CanRequestFlowApprovalForBatchAndAllLines: Boolean;         CanRequestFlowApprovalForBatchAndCurrentLine: Boolean;         CanCancelFlowApprovalForBatch: Boolean;         CanCancelFlowApprovalForLine: Boolean;         BackgroundErrorCheck: Boolean;         ShowAllLinesEnabled: Boolean;         CurrentPostingDate: Date;     protected var         ApplyEntriesActionEnabled: Boolean;         IsSimplePage: Boolean;     local procedure EnableApplyEntriesAction()     begin         ApplyEntriesActionEnabled :=           (Rec.”Account Type” in [Rec.”Account Type”::Customer, Rec.”Account Type”::Vendor, Rec.”Account Type”::Employee]) or           (Rec.”Bal. Account Type” in [Rec.”Bal. Account Type”::Customer, Rec.”Bal. Account Type”::Vendor, Rec.”Bal. Account Type”::Employee]);         OnAfterEnableApplyEntriesAction(Rec, ApplyEntriesActionEnabled);     end;     local procedure CurrentJnlBatchNameOnAfterVali()     begin         CurrPage.SaveRecord();         GenJnlManagement.SetName(CurrentJnlBatchName, Rec);         SetControlAppearanceFromBatch();         CurrPage.Update(false);     end;     procedure SetUserInteractions()     begin         StyleTxt := Rec.GetStyle();     end;     local procedure GetCurrentlySelectedLines(var GenJournalLine: Record “Gen. Journal Line”): Boolean     begin         CurrPage.SetSelectionFilter(GenJournalLine);         exit(GenJournalLine.FindSet());     end;     local procedure GetPostingDate(): Date     begin         if IsSimplePage then             exit(CurrentPostingDate);         exit(Workdate());     end;     internal procedure SetApprovalState(RecordId: RecordId; OpenApprovalEntriesOnJournalBatchExist: Boolean; LocalCanRequestFlowApprovalForBatch: Boolean; var LocalCanCancelFlowApprovalForLine: Boolean; var OpenApprovalEntriesExistForCurrentUser: Boolean; var OpenApprovalEntriesOnJournalLineExist: Boolean; var OpenApprovalEntriesOnBatchOrCurrentJournalLineExist: Boolean; var CanCancelApprovalForJournalLine: Boolean; var LocalCanRequestFlowApprovalForBatchAndCurrentLine: Boolean)     var         ApprovalsMgmt: Codeunit “Approvals Mgmt.”;         WorkflowWebhookManagement: Codeunit “Workflow Webhook Management”;         CanRequestFlowApprovalForLine: Boolean;     begin         OpenApprovalEntriesExistForCurrentUser := OpenApprovalEntriesExistForCurrentUser or ApprovalsMgmt.HasOpenApprovalEntriesForCurrentUser(RecordId);         OpenApprovalEntriesOnJournalLineExist := ApprovalsMgmt.HasOpenApprovalEntries(RecordId);         OpenApprovalEntriesOnBatchOrCurrentJournalLineExist := OpenApprovalEntriesOnJournalBatchExist or OpenApprovalEntriesOnJournalLineExist;         CanCancelApprovalForJournalLine := ApprovalsMgmt.CanCancelApprovalForRecord(RecordId);         WorkflowWebhookManagement.GetCanRequestAndCanCancel(RecordId, CanRequestFlowApprovalForLine, LocalCanCancelFlowApprovalForLine);         LocalCanRequestFlowApprovalForBatchAndCurrentLine := LocalCanRequestFlowApprovalForBatch and CanRequestFlowApprovalForLine;     end;     internal procedure SetApprovalStateForBatch(GenJournalBatch: Record “Gen. Journal Batch”; GenJournalLine: Record “Gen. Journal Line”; var OpenApprovalEntriesExistForCurrentUser: Boolean; var OpenApprovalEntriesOnJournalBatchExist: Boolean; var OpenApprovalEntriesOnBatchOrAnyJournalLineExist: Boolean; var CanCancelApprovalForJournalBatch: Boolean; var LocalCanRequestFlowApprovalForBatch: Boolean; var LocalCanCancelFlowApprovalForBatch: Boolean; var LocalCanRequestFlowApprovalForBatchAndAllLines: Boolean; var LocalApprovalEntriesExistSentByCurrentUser: Boolean; var EnabledGeneralJournalBatchWorkflowsExist: Boolean; var EnabledGeneralJournalLineWorkflowsExist: Boolean)     var         ApprovalsMgmt: Codeunit “Approvals Mgmt.”;         WorkflowWebhookManagement: Codeunit “Workflow Webhook Management”;         WorkflowEventHandling: Codeunit “Workflow Event Handling”;         WorkflowManagement: Codeunit “Workflow Management”;         CanRequestFlowApprovalForAllLines: Boolean;     begin         OpenApprovalEntriesExistForCurrentUser := OpenApprovalEntriesExistForCurrentUser or ApprovalsMgmt.HasOpenApprovalEntriesForCurrentUser(GenJournalBatch.RecordId);         OpenApprovalEntriesOnJournalBatchExist := ApprovalsMgmt.HasOpenApprovalEntries(GenJournalBatch.RecordId);         OpenApprovalEntriesOnBatchOrAnyJournalLineExist := OpenApprovalEntriesOnJournalBatchExist or ApprovalsMgmt.HasAnyOpenJournalLineApprovalEntries(GenJournalLine.”Journal Template Name”, GenJournalLine.”Journal Batch Name”);         CanCancelApprovalForJournalBatch := … Continue reading Enhancing the Recurring General Journal with Automated Approval Workflows in Dynamics 365 Business Central

Managing Profile Pictures on Custom Pages in Microsoft Dynamics 365 Business Central

When creating custom pages in Business Central, sometimes you need to allow users to handle profile pictures. Whether you’re working with a custom Employee Profile page or another entity, it’s crucial to provide intuitive ways for users to manage their pictures. In this blog, we’ll walk through the process of implementing four key actions for handling profile pictures in custom pages: These features can be achieved using AL (the programming language for Business Central). Setting Up the Custom UserProfile Page Let’s first define the User Profile page where the user can manage their profile picture. This page will provide the fields for Profile ID, Profile Name, and a FactBox to display the profile picture. Code: page 50213 “UserProfileCard” {     PageType = Card;     SourceTable = “UserProfile”;     ApplicationArea = All;     Caption = ‘User Profile’;     layout     {         area(content)         {             group(Group)             {                 field(“Profile ID”; Rec.”Profile ID”)                 {                     ApplicationArea = All;                 }                 field(“Profile Name”; Rec.”Profile Name”)                 {                     ApplicationArea = All;                 }             }         }         // FactBox area to display profile picture         area(factboxes)         {             part(“Profile Picture FactBox”; “ProfilePictureFactBoxPart”)             {                 ApplicationArea = All;             }         }     }     actions     {         area(Processing)         {             action(“Take Picture”)             {                 ApplicationArea = All;                 trigger OnAction()                 var                     Camera: Codeunit “Camera”;                     InS: InStream;                     PicName: Text;                 begin                     // Validate the selected profile                     if not IsProfileSelected(Rec.”Profile ID”) then                         exit;                     // Check if the camera is available                     if Camera.IsAvailable() then begin                         // Get and import the picture                         if Camera.GetPicture(InS, PicName) then begin                             // Import the picture into the profile record                             Rec.”Profile Picture”.ImportStream(InS, PicName);                             Rec.Modify(); // Save the modified record                             Message(‘Profile picture updated successfully.’);                         end                         else                             Message(‘Failed to capture the picture. Try again.’);                     end                     else                         Message(‘No camera detected. Please connect a camera.’);                 end;             }             fileuploadaction(“Import Picture”)             {                 ApplicationArea = All;                 Caption = ‘Import’;                 Image = Import;                 ToolTip = ‘Import a picture file.’;                 trigger OnAction(Files: List of [FileUpload])                 var                     FileName: Text;                     InStream: InStream;                     FileUpload: FileUpload;                 begin                     Rec.TestField(“Profile ID”);                     if Rec.”Profile Name” = ” then                         Error(MustSpecifyNameErr);                     if Rec.”Profile Picture”.HasValue() then                         if not Confirm(OverrideImageQst) then                             exit;                     // Ensure the file is valid                     if Files.Count = 0 then                         Error(‘No file selected.’);                     // Iterate through the Files list (typically just one file)                     foreach FileUpload in Files do begin                         // Create the InStream from the current file                         FileUpload.CreateInStream(InStream);                         Rec.”Profile Picture”.ImportStream(InStream, FileName);                     end;                     Rec.Modify(true);                     Message(‘Picture imported successfully: %1’, FileName);                 end;             }             action(“Export Picture”)             {                 ApplicationArea = All;                 Caption = ‘Export’;                 Image = Export;                 ToolTip = ‘Export the picture to a file.’;                 trigger OnAction()                 var                     FileName: Text;                     OutStream: OutStream;                     InStream: InStream;                     TempBlob: Codeunit “Temp Blob”; // Helps with the stream conversion                 begin                     Rec.TestField(“Profile ID”);                     Rec.TestField(“Profile Name”);                     // Ensure there’s a profile picture to export                     if not Rec.”Profile Picture”.HasValue() then begin                         Message(‘No profile picture found to export.’);                         exit;                     end;                     // Generate a file name for the exported picture                     FileName := Rec.”Profile Name” + ‘_ProfilePicture.jpg’;                     // Export the image to an OutStream via TempBlob                     TempBlob.CreateOutStream(OutStream); // Prepare the OutStream                     Rec.”Profile Picture”.ExportStream(OutStream); // Export the Media field content into the OutStream                     TempBlob.CreateInStream(InStream); // Create InStream from TempBlob to use for download                     // Trigger the file download                     DownloadFromStream(InStream, FileName, ”, ”, FileName);                     // Show success message                     Message(‘Profile picture exported successfully as %1’, FileName);                 end;             }             action(“Delete Picture”)             {                 ApplicationArea = All;                 Caption = ‘Delete’;                 Image = Delete;                 ToolTip = ‘Delete the picture.’;                 trigger OnAction()                 begin                     Rec.TestField(“Profile ID”);                     if not Confirm(DeleteImageQst) then                         exit;                     Clear(Rec.”Profile Picture”);                     Rec.Modify(true);                 end;             }         }     }     trigger OnAfterGetCurrRecord()     begin         SetEditableOnPictureActions();     end;     trigger OnOpenPage()     begin         CameraAvailable := Camera.IsAvailable();     end;     var         Camera: Codeunit Camera;         CameraAvailable: Boolean;         OverrideImageQst: Label ‘The existing picture will be replaced. Do you want to continue?’;         DeleteImageQst: Label ‘Are you sure you want to delete the picture?’;         SelectPictureTxt: Label ‘Select a picture to upload’;         FileManagement: Codeunit “File Management”;         MustSpecifyNameErr: Label ‘You must specify a profile name before you can import a picture.’;     local procedure SetEditableOnPictureActions()     begin         // Enabling or disabling the delete/export actions based on whether a picture is present.     end;     // Function to check if a profile is selected     procedure IsProfileSelected(ProfileID: Code[20]): Boolean     begin         if ProfileID = ” then begin             Message(‘Please select a profile first!’);             exit(False);         end;         exit(True);     end; } Understanding the Key Actions Let’s break down the actions implemented in this custom UserProfile page: 1. Take Picture This action utilizes the Camera Codeunit to capture a picture. If the camera is connected, it will fetch an image and store it in the “Profile Picture” field. 2. Import (Upload) Picture The Import Picture action allows users to upload a picture from their local system into the “Profile Picture” field. It uses the FileUpload control and confirms if the existing image should be replaced. 3. Export Picture The Export Picture action downloads the profile picture to the user’s system. The image is exported to an OutStream, then triggered for download using DownloadFromStream. 4. Delete Picture The Delete Picture action clears the profile picture field. It prompts for confirmation before removing the image. Benefits To encapsulate, in standard Business Central, the functionality for managing user profiles and pictures is built in. However, when working with custom pages, you often need to implement these features manually. By using the actions … Continue reading Managing Profile Pictures on Custom Pages in Microsoft Dynamics 365 Business Central

Understanding and Analyzing Customer Ledger Data with Business Charts in Dynamics 365

In today’s business world, understanding your financial data is crucial for making informed decisions. One of the key areas of focus for businesses is tracking customer payments and outstanding invoices. With Dynamics 365, you can leverage customer ledger entries to provide visual insights into customer behaviors, payment patterns, and outstanding amounts. These insights help businesses optimize collections, improve cash flow, and make data-driven decisions. In this blog, we’ll explore how to analyze Customer Ledger Entries through Business Charts in Dynamics 365, focusing on Outstanding Invoices, Payments Applied, and Aging of Outstanding Amounts. What Are Customer Ledger Entries? Customer Ledger Entries in Dynamics 365 track all transactions related to a customer, including invoices, payments, credit memos, and adjustments. Each entry contains details such as: By analyzing this data, businesses can gain valuable insights into a customer’s payment habits, outstanding debts, and the status of their invoices. Why Use Business Charts? Business Charts in Dynamics 365 provide a visual representation of your data, making it easier to spot trends and gain actionable insights. Instead of manually sorting through customer ledger entries, you can use charts to instantly assess: This allows teams to make timely decisions about follow-ups with customers and plan for collections. Creating Charts to Analyze Customer Ledger Data Let’s dive into some key charting logic you can apply to Customer Ledger Entries in Dynamics 365 to get more detailed insights into your data. 1. Outstanding Invoices (Remaining Amount per Invoice) The first and most essential data point to track is the Remaining Amount of each invoice. By grouping this data by invoice number, you can quickly identify which invoices are outstanding and need to be followed up. Logic: Buffer.AddMeasure(‘Remaining Amount’, 2, Buffer.”Data Type”::Decimal, ChartType.AsInteger()); Buffer.SetXAxis(‘Document No.’, Buffer.”Data Type”::String); // Group by invoice number The chart will help visualize which invoices are outstanding and need to be prioritized for payment. Code page 50215 “Business Charts” {     ApplicationArea = All;     Caption = ‘Business Charts’;     PageType = CardPart;     UsageCategory = Administration;     layout     {         area(Content)         {             usercontrol(chart; BusinessChart)             {                 ApplicationArea = All;                 trigger AddInReady()                 var                     Buffer: Record “Business Chart Buffer” temporary;                     CustLedgerEntry: Record “Cust. Ledger Entry”;                     Customer: Record Customer;                     ChartType: Enum “Business Chart Type”;                     AppliedAmount: Decimal;                     RemainingAmount: Decimal;                     s: Integer;                 begin                     // Initialize the chart buffer and variables                     Buffer.Initialize();                     ChartType := “Business Chart Type”::Pie; // Use a bar chart for better visual representation                     // Add measure for ‘Remaining Amount’                     Buffer.AddMeasure(‘Remaining Amount’, 2, Buffer.”Data Type”::Decimal, ChartType.AsInteger());                     // Set X-axis to ‘Invoice No.’ for grouping data by invoice                     Buffer.SetXAxis(‘Document No.’, Buffer.”Data Type”::String);                     // Loop through all customers                     if Customer.FindSet() then begin                         repeat                             // Loop through Customer Ledger Entries to accumulate remaining amounts                             if CustLedgerEntry.FindSet() then begin                                 repeat                                     CustLedgerEntry.CalcFields(“Remaining Amount”);                                     // Only accumulate amounts for the current customer based on Customer No.                                     if CustLedgerEntry.”Customer No.” = Customer.”No.” then begin                                         // If it is an Invoice, accumulate Remaining Amount                                         if CustLedgerEntry.”Document Type” = “Gen. Journal Document Type”::Invoice then begin                                             Buffer.AddColumn(CustLedgerEntry.”Document No.”);  // Label by Invoice No.                                             Buffer.SetValueByIndex(0, s, CustLedgerEntry.”Remaining Amount”);  // Set RemainingAmount for the invoice                                             s += 1;                                         end;                                     end;                                 until CustLedgerEntry.Next() = 0;                             end;                         until Customer.Next() = 0;                     end;                     // Update the chart with the accumulated data                     if s > 0 then                         Buffer.UpdateChart(CurrPage.Chart)                     else                         Message(‘No outstanding invoices to display in the chart.’);                 end;             }         }     } } 2. Payments Applied (Amount Applied to Invoices) Another important metric is the Amount Applied to customer invoices. Tracking payments allows you to understand customer payment behavior and outstanding balances. By focusing on Payments, you can track how much a customer has paid against their total balance. Logic: Buffer.AddMeasure(‘Amount Applied’, 2, Buffer.”Data Type”::Decimal, ChartType.AsInteger()); Buffer.SetXAxis(‘Customer No.’, Buffer.”Data Type”::String); // Group by customer This chart will help businesses track customer payments and identify any customers with overdue payments. 3. Aging of Outstanding Amounts (Bucketed by Days Overdue) Aging reports are an essential tool for understanding the timeliness of payments. By grouping outstanding amounts into aging buckets (e.g., 0-30 days, 31-60 days, etc.), businesses can better assess which invoices are overdue and prioritize collection efforts. Logic: // Calculate aging based on Due Date if (Today – CustLedgerEntry.”Due Date”) <= 30 then     AgingBucket := ‘0-30 Days’ elseif (Today – CustLedgerEntry.”Due Date”) <= 60 then     AgingBucket := ’31-60 Days’ Buffer.SetXAxis(‘Aging Bucket’, Buffer.”Data Type”::String); // Group by aging bucket This chart will provide a clear picture of which invoices are overdue and for how long, helping businesses prioritize collections. Benefits of Using Business Charts for Customer Ledger Analysis By leveraging Customer Ledger Entries and Business Charts in Dynamics 365, businesses can transform raw data into valuable insights. Visualizing outstanding invoices, payments applied, and aging amounts helps businesses prioritize collections, forecast cash flow, and ultimately improve their financial health. These charts make it easier for accounting and finance teams to manage customer payments and reduce the risk of overdue balances. The ability to track customer behavior and quickly identify payment issues gives businesses a competitive edge, helping them maintain a healthy cash flow and strong customer relationships. We hope you found this blog useful, and if you would like to discuss anything, you can reach out to us at transform@cloudfonts.com.

SEARCH :

FOLLOW CLOUDFRONTS BLOG :

FOLLOW CLOUDFRONTS BLOG :


Secured By miniOrange