Enhancing Number Series in Business Central: New Architecture and Copilot Integration
As Business Central continues to advance, its features and functionalities are also evolving. One significant enhancement is the introduction of a new series system. This update is designed to simplify the process of assigning numbers to various documents, ensuring both consistency and efficiency. In this blog, we’ll explore the core aspects of this new number series and how you can leverage it in your Business Central environment. Understanding the Basics Number series in Business Central serve as unique identifiers for documents such as sales orders, purchase orders, and invoices. These identifiers play a crucial role in effectively tracking and managing documents. With the introduction of the new number series, improvements have been made to enhance flexibility and provide better control over these identifiers. What’s New? Previously, Business Central used the NoSeriesManagement codeunit for managing number series. The updated system introduces two distinct entities: This new structure brings a more organized and streamlined approach to number series management, enabling enhanced customization for different document types and processes. Refactoring Your Code With these updates, the NoSeriesManagement codeunit is now marked for deprecation. When you use it, you may encounter a warning like: “Codeunit ‘NoSeriesManagement’ is marked for removal. Reason: Please use the ‘No. Series’ and ‘No. Series – Batch’ codeunits instead. Tag: 24.0”. Here’s a step-by-step guide to refactoring your code for the new system: 1. Identify Usage First, locate all instances where the NoSeriesManagement codeunit is referenced in your codebase. This includes direct calls or any references to its functions. 2. Replace with New Codeunits Update these references to use the appropriate new codeunit: How to Implement the New Codeunits a. Using “No. Series” This codeunit is used for standard number series management tasks. Below is an example of how it works: var NoSeries: Codeunit “No. Series”;begin NoSeries.GetLastNoUsed(); NoSeries.GetNextNo();end; b. Using “No. Series – Batch” The “No. Series – Batch” codeunit is designed for efficient batch processing of multiple number series. Use its methods like PeekNextNo to retrieve the next number without modifying the series. var NoSeriesBatch: Codeunit “No. Series – Batch”;begin NoSeriesBatch.GetLastNoUsed(); NoSeriesBatch.PeekNextNo();end; Example: Before and After Refactoring Before Refactoring: After Refactoring: Suggesting No. Series Using Copilot in Business Central Business Central’s Copilot integration simplifies generating and managing No. Series for different modules. Follow this step-by-step guide to utilize this feature effectively: 2. Create a New Number Series 3. Generate Number Series for a Specific Module 4. Modify an Existing Number Series 5. Prepare Number Series for the Next Year This feature empowers users to efficiently manage No. Series with minimal manual effort, ensuring consistency and saving valuable time. Explore the Copilot suggestions to optimize your workflow in Business Central! To conclude, the advancements in Business Central’s Number Series management, with the introduction of the new architecture and Copilot integration, offer a significant leap in flexibility, efficiency, and user experience. The updated “No. Series” and “No. Series – Batch” codeunits streamline workflows, while Copilot simplifies the creation, modification, and futureproofing of number series with intelligent suggestions. By adopting these features, businesses can ensure consistency, reduce manual errors, and save valuable time, making their operations more streamlined and future ready. Explore these enhancements today to unlock the full potential of Business Central! 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.
Share Story :
Implementing Encryption and Decryption Functions via API in Business Central
In Microsoft Dynamics 365 Business Central, securing sensitive data like passwords is crucial for protecting both businesses and customers. By the end of this post, you will understand how passwords are encrypted before being stored and decrypted only when necessary. This ensures that passwords remain secure, minimizing exposure and building trust. 1. Storing Encrypted Passwords Passwords are encrypted before being stored in the database to ensure they are protected. This prevents anyone from accessing passwords in plain text, even if they gain unauthorized access to the database. Here’s an example of how a custom table, CFSCustomPage, stores encrypted passwords using the EncodePassword procedure. In the above code, the password is encrypted before being stored in the database to ensure the data remains secure. The EncodePassword procedure is used for encrypting the password. 2. Decrypting Passwords When Needed When passwords need to be retrieved, they are decrypted using the DecodePassword procedure. This ensures that passwords are only accessible when required, minimizing exposure. Here’s the decryption function: In the above code, passwords are decrypted only when needed, allowing the system to securely handle sensitive data. 3. Returning Decrypted Passwords via API For external systems or applications requiring access to customer data, Business Central ensures that passwords are only decrypted and transmitted securely via API. This way, customer information remains protected throughout the process. Here’s how the API Page CFSCustPageAPI handles this: The API exposes the encrypted password and only decrypts it when accessed securely through the API. 4. Returning Decrypted Passwords in a List Page Passwords can also be decrypted and displayed in a list page when necessary, ensuring they are only shown to authorized users. This is done using the DecodePassword function in the page CFSCustomPage, as shown below: This page allows authorized users to view encrypted passwords, MD5, SHA1, SHA512, and AES encrypted passwords, and also decrypt them when necessary. 5. Best Practices for Password Security 6. Encryption Types Used in the System Here are the different types of encryptions and hashing methods used in Business Central: Code: table 71983576 CFSCustomPage { Caption = ‘CustomPage’; DataClassification = ToBeClassified; fields { field(71983575; “No.”; Code[20]) { Caption = ‘No.’; DataClassification = CustomerContent; } field(71983576; Name; Text[50]) { Caption = ‘Name’; DataClassification = CustomerContent; } field(71983577; Password; Text[365]) { Caption = ‘Password’; DataClassification = CustomerContent; } } keys { key(PK; “No.”) { Clustered = true; } } // Procedure to encode (encrypt) a password [ServiceEnabled] procedure EncodePassword(var Password: Text[365]) var EncryptedPassword: Text[365]; begin // Encrypt the password using the Encrypt function EncryptedPassword := Encrypt(Password); // Assign the encrypted password back to the Password field Password := EncryptedPassword; end; // Procedure to decode (decrypt) the password [ServiceEnabled] procedure DecodePassword(EncryptedPassword: Text[365]): Text var DecryptedPassword: Text; begin // Decrypt the password using the Decrypt function DecryptedPassword := Decrypt(EncryptedPassword); // Return the decrypted password exit(DecryptedPassword); end; // Procedure to generate MD5 hash of a password procedure MD5Hash(Pwd: Text): Text var CryptographyManagement: Codeunit “Cryptography Management”; HashAlgorithmType: Option MD5,SHA1,SHA256,SHA384,SHA512; begin // Generate and return the MD5 hash of the password exit(CryptographyManagement.GenerateHash(Pwd, HashAlgorithmType::MD5)); end; // Procedure to generate SHA1 hash of a password procedure SHA1(Pwd: Text): Text var CryptographyManagement: Codeunit “Cryptography Management”; HashAlgorithmType: Option MD5,SHA1,SHA256,SHA384,SHA512; begin // Generate and return the SHA1 hash of the password exit(CryptographyManagement.GenerateHash(Pwd, HashAlgorithmType::SHA1)); end; // Procedure to generate SHA512 hash of a password procedure SHA512(Pwd: Text): Text var CryptographyManagement: Codeunit “Cryptography Management”; HashAlgorithmType: Option MD5,SHA1,SHA256,SHA384,SHA512; begin // Generate and return the SHA512 hash of the password exit(CryptographyManagement.GenerateHash(Pwd, HashAlgorithmType::SHA512)); end; // AES Encryption and Decryption example procedure AESEncryptionExample(Passowrd: Text): TEXT var RijndaelCryptography: Codeunit “Rijndael Cryptography”; Base64Convert: Codeunit “Base64 Convert”; EncryptedText: Text; DecryptedText: Text; SecretTexts: SecretText; begin // Convert the encryption key ‘1106198321061984’ to Base64 format and store it in SecretTexts SecretTexts := Base64Convert.ToBase64(‘1106198321061984’); // Set the encryption key and initialization vector (IV) using Base64-encoded values RijndaelCryptography.SetEncryptionData(SecretTexts, Base64Convert.ToBase64(‘ITGateWayXyZ1234’)); // Set the AES block size to 128 bits (standard AES block size) RijndaelCryptography.SetBlockSize(128); // Set the cipher mode for AES to CBC (Cipher Block Chaining) RijndaelCryptography.SetCipherMode(‘CBC’); // Set the padding mode for AES encryption to PKCS7 (standard padding scheme for block ciphers) RijndaelCryptography.SetPaddingMode(‘PKCS7’); // Encrypt the password using AES encryption EncryptedText := RijndaelCryptography.Encrypt(Passowrd); // Decrypt the encrypted password using … Continue reading Implementing Encryption and Decryption Functions via API in Business Central
Share Story :
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.
Share Story :
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.
Share Story :
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
Share Story :
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
Share Story :
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.
Share Story :
Mastering Date Manipulation with CALCDATE in Microsoft Dynamics 365 Business Central
Microsoft Dynamics 365 Business Central provides a comprehensive suite of tools designed to streamline business processes, and one of the most powerful tools for managing dates and times is the CALCDATE function. This versatile function enables users to perform complex date calculations with ease, making it indispensable for developers, consultants, and power users. In this blog post, we’ll dive deep into the CALCDATE function, explain its syntax, and explore how you can leverage it in your Business Central environment. Understanding CALCDATE The CALCDATE function is used to calculate a new date based on a specific date expression and an optional reference date. It is particularly helpful in scenarios where you need to determine dates relative to a given point in time. This could include calculating due dates, forecasting future events, setting up recurring transactions, or determining any other date relative to the system’s current or a user-defined date. For example, if you need to find the first day of next month or calculate a due date based on the current date, CALCDATE can handle these tasks efficiently. Syntax of CALCDATE The syntax of the CALCDATE function is simple, but the power lies in how you use the date expressions to represent relative time periods. NewDate := System.CalcDate(DateExpression: Text [, Date: Date]) Parameters DateExpression (Type: Text): This is the key input to the function, where you specify the date you want to calculate. The date expression can represent a variety of time periods, ranging from days to weeks, months, quarters, and years. The expression is evaluated from left to right, and each subexpression is processed one at a time. The valid syntax for the date expression follows a set of rules: Subexpression: A date expression consists of one or more subexpressions, each of which may be prefixed with a + or – sign. The subexpression can specify a time unit (day, week, month, etc.) along with a number. Here’s the structure of a typical date expression: <Subexpression> = [<Sign>] <Term> <Sign> = + | – <Term> = <Number><Unit> | <Unit><Number> | <Prefix><Unit> Examples of valid date expressions: The calendar in Business Central starts on Monday and ends on Sunday, where Monday is considered weekday 1 and Sunday is weekday 7. An invalid date expression, such as specifying an incorrect syntax, will result in a runtime error. 2. [Optional] Date (Type: Date):This optional parameter is used to define the reference date. If you omit it, the system defaults to the current date. You can specify any date here, and CALCDATE will perform the calculation based on that reference date instead of the current system date. Return Value Example: pageextension 50103 CustomerPageExt1 extends “Customer Card” { trigger OnOpenPage() var StartDate: Date; EndDate: Date; FirstDateofPreviousMonth: Date; LastDateofPreviousMonth: Date; FirstDateofNextMonth: Date; LastDateofNextMonth: Date; TodayDate: Date; FirstDateofYear: Date; LastDateofYear: Date; FirstDayOfNextQuarter: Date; LastDayOfCurrentQuarter: Date; FirstDayOfNextWeek: Date; FirstDayOfNextWeek10D: Date; begin // Current Month Start and End Dates StartDate := System.CalcDate(‘<-CM>’, Today); EndDate := System.CalcDate(‘<CM>’, Today); // Previous Month Start and End Dates TodayDate := TODAY; FirstDateOfPreviousMonth := CALCDATE(‘<-1M>’, CALCDATE(‘<-CM>’, TodayDate)); LastDateOfPreviousMonth := CALCDATE(‘<-1M>’, CALCDATE(‘<CM>+1D’, TodayDate) – 1); // Next Month Start and End Dates FirstDateOfNextMonth := CALCDATE(‘<+1M>’, CALCDATE(‘<-CM>’, TodayDate)); LastDateOfNextMonth := CALCDATE(‘<+1M>’, CALCDATE(‘<CM>+1D’, TodayDate) – 1); // First and Last Date of the Current Year FirstDateofYear := CALCDATE(‘<-CY>’, TodayDate); LastDateOfYear := CALCDATE(‘<CY>’, TODAY); // First Day of the Next Quarter FirstDayOfNextQuarter := CALCDATE(‘<+1Q>’, CALCDATE(‘<-CQ>’, TodayDate)); // Last Day of the Current Quarter LastDayOfCurrentQuarter := CALCDATE(‘<CQ>’, TODAY); // First Day of the Next Week FirstDayOfNextWeek := CALCDATE(‘<+1W>’, CALCDATE(‘<-CW>’, TodayDate)); // First Day of the Next Week + 10D FirstDayOfNextWeek10D := CALCDATE(‘<+1W>+10D’, CALCDATE(‘<-CW>’, TodayDate)); Message( ‘Current Month: ‘ + ‘\’ + ‘Start Date: %1, End Date: %2’ + ‘\’ + ‘\’ + ‘Previous Month: ‘ + ‘\’ + ‘Start Date: %3, End Date: %4’ + ‘\’ + ‘\’ + ‘Next Month: ‘ + ‘\’ + ‘Start Date: %5, End Date: %6’ + ‘\’ + ‘\’ + ‘Current Year: ‘ + ‘\’ + ‘Start Date: %7, End Date: %8’ + ‘\’ + ‘\’ + ‘Next Quarter: ‘ + ‘\’ + ‘Start Date: %9’ + ‘\’ + ‘\’ + ‘Current Quarter: ‘ + ‘\’ + ‘End Date: %10’ + ‘\’ + ‘\’ + ‘Next Week: ‘ + ‘\’ + ‘Start Date: %11’ + ‘\’ + ‘\’ + ‘Next Week + 10D: ‘ + ‘\’ + ‘Start Date: %12’, StartDate, EndDate, FirstDateOfPreviousMonth, LastDateOfPreviousMonth, FirstDateOfNextMonth, LastDateOfNextMonth, FirstDateofYear, LastDateOfYear, FirstDayOfNextQuarter, LastDayOfCurrentQuarter, FirstDayOfNextWeek, FirstDayOfNextWeek10D ); end; } Why Use CALCDATE in Business Central? The CALCDATE function is incredibly useful for automating and simplifying date-based calculations in Microsoft Dynamics 365 Business Central. Whether you are calculating due dates, generating reports based on time periods, or working with recurring events, CALCDATE saves time and reduces the chances of errors by automating these calculations. Here are some scenarios where CALCDATE can be particularly useful: To conclude, the CALCDATE function is a vital tool for anyone working in Microsoft Dynamics 365 Business Central. It simplifies the process of calculating dates based on specific time intervals, allowing users to manage and manipulate time-based data with ease. By understanding its syntax and functionality, you can unlock the full potential of CALCDATE and streamline your business processes. If you’re a developer or power user, mastering the CALCDATE function will not only enhance your efficiency but also give you greater control over your business data and operations. 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.
Share Story :
Unlocking the Power of Ternary Operators and Extendable Interfaces in Business Central
Developers are continually looking for ways to write cleaner, more efficient code. Two powerful tools that have emerged to meet this need are the ternary operator and extendable interfaces. This blog explores how these features can enhance your AL code, making it more readable and maintainable. Ternary Operator in Business Central The ternary operator, introduced in the 2024 release wave 2 of Business Central, is a concise way to perform conditional assignments. It offers a streamlined alternative to traditional if-else statements, promoting code clarity and reducing verbosity. Syntax and Example The ternary operator in AL has the following syntax: condition ? exprIfTrue : exprIfFalse Here’s an example that demonstrates its usage: pageextension 50300 CustomerListExtension extends “Customer List” {layout { addlast(Content) { field(“Customer Status”; IsCustomerActive) { ApplicationArea = All; } }} var IsCustomerActive: Text; trigger OnAfterGetCurrRecord();begin IsCustomerActive := Rec.Blocked = Rec.Blocked::” ” ? ‘Active’ : ‘Inactive’;end;} In this example, the ternary operator is used to determine whether a customer is active or inactive based on their Blocked status. The result is a concise and more readable conditional assignment. Extendable Interfaces in Business Central Extendable interfaces provide a modular and flexible way to define reusable logic across different components in Business Central. They allow developers to create scalable systems that can easily adapt to changing business requirements. Defining and Implementing Extendable Interfaces Base Interface: interface INotificationProvider {procedure SendNotification(Message: Text): Text;} Extended Interface: interface INotificationProviderExt extends INotificationProvider {procedure SendEmailNotification(Message: Text): Text;procedure SendSMSNotification(Message: Text): Text;} Implementing the Interfaces in Codeunits: Email Notification Provider:codeunit 50301 EmailNotificationProvider implements INotificationProvider {procedure SendNotification(Message: Text): Text;begin exit(‘Email sent with message: ‘ + Message);end;} SMS Notification Provider:codeunit 50302 SMSNotificationProvider implements INotificationProvider {procedure SendNotification(Message: Text): Text;begin exit(‘SMS sent with message: ‘ + Message);end;} Advanced Notification Provider:codeunit 50303 AdvancedNotificationProvider implements INotificationProviderExt {procedure SendNotification(Message: Text): Text;begin exit(‘Notification sent with message: ‘ + Message);end; procedure SendEmailNotification(Message: Text): Text;begin exit(‘Email sent with message: ‘ + Message);end; procedure SendSMSNotification(Message: Text): Text;begin exit(‘SMS sent with message: ‘ + Message);end;} Real-World Application Let’s implement these interfaces in a page extension to add actions for sending notifications to customers. pageextension 50300 CustomerListExt extends “Customer List” {actions { addafter(ApplyTemplate) { action(SendEmail) { ApplicationArea = All; Image = Email; Caption = ‘Send Email’; Promoted = true; PromotedCategory = Process; trigger OnAction() begin iNotificationProvider := EmailNotificationProvider; Message(iNotificationProvider.SendNotification(‘Email message to customer’)); end; } action(SendSMS) { Image = Phone; Caption = ‘Send SMS’; ApplicationArea = All; Promoted = true; PromotedCategory = Process; trigger OnAction() begin iNotificationProvider := SMSNotificationProvider; Message(iNotificationProvider.SendNotification(‘SMS message to customer’)); end; } action(SendAdvancedNotification) { Image = Notification; Caption = ‘Send Advanced Notification’; ApplicationArea = All; Promoted = true; PromotedCategory = Process; trigger OnAction() begin iNotificationProviderExt := AdvancedNotificationProvider; Message(iNotificationProviderExt.SendEmailNotification(‘Advanced Email message to customer’)); Message(iNotificationProviderExt.SendSMSNotification(‘Advanced SMS message to customer’)); end; } }} var iNotificationProvider: Interface INotificationProvider; iNotificationProviderExt: Interface INotificationProviderExt; EmailNotificationProvider: Codeunit EmailNotificationProvider; SMSNotificationProvider: Codeunit SMSNotificationProvider; AdvancedNotificationProvider: Codeunit AdvancedNotificationProvider;} This example demonstrates how to use extendable interfaces to create a flexible and maintainable notification provider system in Business Central, allowing for different types of notifications to be added seamlessly. Conclusion The ternary operator and extendable interfaces in Business Central are powerful tools that can significantly enhance your AL code. By using the ternary operator, you can streamline conditional logic and improve code readability. Extendable interfaces, on the other hand, allow for modular, scalable solutions that can adapt to changing business needs. Embrace these features to build more efficient, maintainable, and future-proof solutions in Business Central.
Share Story :
Seamlessly Integrating Shopify with Business Central: A Comprehensive Guide
This guide provides a step-by-step walkthrough for integrating Shopify with Business Central (OnCloud). The integration focuses on synchronizing key aspects such as inventory, product details, and order information to enable efficient management of your Shopify store directly from Business Central. With this integration, you can streamline your eCommerce operations and ensure real-time data alignment between both platforms. Pre-requisites: Before beginning the integration, ensure you have the following: Steps for Shopify and Business Central Integration 1. Create an Account on Shopify – Go to Shopify Admin and create your account. – Shopify offers a 3-day free trial, so you can explore the platform before committing. 2. Access the Shopify Dashboard – After successfully creating your Shopify account, you’ll be directed to the Shopify dashboard. – From here, copy the Shopify store URL, as you’ll need it later during the integration with Business Central. 3. Navigate to Business Central – Open Business Central and search for “Shopify Shops” in the global search bar. – Click on New to add a new Shopify shop. 4. Enter Shopify Shop Information – In the new Shopify shop creation screen, enter a unique Code for the shop. – Paste the Shopify URL (copied from step 2) into the required field. 5. Set Shopify Location – In Business Central, go to Shopify Location settings. – Select the relevant location for the shop. 6. Set Stock Calculation – Choose Free Inventory for Stock Calculation. This option ensures that your available inventory is always in sync with Shopify. 7. Add Products in Business Central – First, click on the Products section in Business Central. – Then, click on Add Items to begin adding products to be synced with Shopify. 8. Sync Inventory – Set the Sync Inventory field to True by enabling the corresponding boolean field. – Enter an appropriate Item Category Code for the products, then click OK to confirm. Optional: Sync Product Images – If you wish to sync product images between Shopify and Business Central, select the Sync Item Images to Shopify option. – By enabling this setting, the images of your items will also be synchronized when the products are added to Shopify. 9. Inventory Sync in Shopify – After completing the previous steps, your inventory will be successfully synced from Business Central to Shopify. Any changes made to stock levels in Business Central will now automatically update in Shopify. 10. If you want to sync shopify to business central go to Shopify Shop Card > Select “From Shopify” in Sync Item. 11. After that go to Synchronization and click on sync products By this if you had added product in shopify it will get sync to business central. 12. Customer Synchronization – You can also synchronize customer information between the two platforms. – For example, once you sync, you’ll see that Meagan has been successfully synchronized to Shopify. 13. View Your Online Store – Now you can view your online store and see your products live on Shopify. Theme Customization in Shopify The look and feel of your Shopify store is important in building a strong brand presence. Shopify offers a variety of customizable themes that you can select and edit to match your brand’s identity. How to Select a Theme: How to Set Up Payments on Shopify? Shopify Payments is an integrated payment gateway that simplifies the transaction process for your Shopify store. Here’s how to set it up to ensure your customers can make secure payments directly on your store. Important Points to Consider Before Setting Up Shopify Payments: – Bank Account Location: Ensure that your bank account is in the same country as your Shopify store. – Enable Two-Step Authentication: For enhanced security, activate two-step authentication before setting up Shopify Payments. – Transaction Fees: Be aware that Shopify Payments charges fees for each transaction, which vary depending on your pricing plan. – Minimum Payout Threshold: Shopify Payments does not process payouts below $1, £1, or €1. These smaller amounts will be added to the next payout that meets the threshold. Did You Know? For U.S.-based stores, Shopify Payments incurs a 1% fee for cross-border transactions (for credit card payments made with cards issued outside the U.S.). Step-by-Step Guide to Setting Up Shopify Payments Step 1: Set Your Store Currency Before you begin, establish the currency for your store. This currency may differ from that of your bank account. Changing the store currency after setup will require contacting Shopify Support. To set your currency: – Navigate to Settings > General > Store defaults > Currency display. – Click on Change store currency and select your preferred currency. – Click Save to implement the changes. Step 2: Access Payment Settings Once you’ve set your store currency, return to the Settings menu and choose the Payment option to initiate the payment setup process. Note: It is essential to complete your Shopify Payments account setup within 21 days of your first sale. This includes providing your business and banking details. For merchants located in the European Union or Hong Kong, setting up Shopify Payments is necessary to accept customer payments. Step 3: Activate Shopify Payments To enable Shopify Payments, you first need to create a Stripe account. Then: – Navigate to the Payment settings page in Shopify. – Click the Activate button for Shopify Payments. If you’re transitioning from another payment provider, Shopify offers an easy way to make this switch. Step 4: Select Your Business Type During the activation of Shopify Payments, you must identify your business type: – Individual: For sole proprietors who haven’t formally registered their business. – Registered Business: For businesses operating under a registered name, such as a corporation, LLC, or partnership. – Non-Profit: For organizations that are officially recognized as non-profit entities. Step 5: Designate an Account Representative Setting up Shopify Payments requires appointing an account representative. This individual, typically the owner, senior executive, or director, must possess the authority to make decisions within the business. Their role is crucial for verification with Shopify’s banking partners. Step 6: … Continue reading Seamlessly Integrating Shopify with Business Central: A Comprehensive Guide
