Tag Archives: Dynamics 365
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.
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.
A Guide to Batch and Serial Numbers in D365 F&O: Part 1
In today’s fast-moving world, keeping track of inventory is incredibly important for businesses of all types and sizes. Proper inventory tracking ensures that businesses can maintain product quality, comply with industry regulations, and deliver better customer experiences. Dynamics 365 Finance & Operations (D365F&O) offers powerful tools to simplify this process, including the use of batch and serial numbers. These features allow businesses to track and manage products accurately, from the time they are manufactured or received to when they reach the customer. Let’s take a closer look at what batch and serial numbers are, how they work in D365F&O, and why they are essential for efficient inventory management. What Are Batch and Serial Numbers? Batch Numbers: Batch numbers are unique identifiers used to group items that were manufactured or received under similar conditions, such as during the same production run or shipment. These numbers make it easier to track and manage items collectively. Batch numbers are particularly useful for businesses that deal with perishable goods or need to maintain strict quality control. For example: Serial Numbers: Serial numbers are unique codes assigned to individual items, allowing each product to be tracked separately. Unlike batch numbers, which apply to groups of items, serial numbers provide item-level traceability. This is especially important for businesses dealing with high-value or complex products. For example: How to Configure Batch and Serial Numbers in D365F&O D365F&O makes it simple to set up and manage batch and serial numbers, ensuring smooth inventory operations. Here’s how you can configure these features step by step: 1. Set Up Tracking Dimensions: 2.Assign Tracking Dimensions to Items: 3.Enable Automatic Numbering: 4.Test and Train: Why Are Batch and Serial Numbers Important? Batch and serial numbers play a crucial role in modern inventory management, offering a range of benefits that streamline operations and reduce risks. Some key advantages include: To conclude, this blog is Part 1 of our detailed guide on batch and serial numbers in D365F&O. We’ve explored what batch and serial numbers are, why they’re important, and how to set them up in the system. By implementing these features, businesses can improve traceability, enhance inventory accuracy, and ensure compliance with industry regulations. In the next part, we’ll take a practical look at how to use batch and serial numbers in day-to-day operations. This will include real-world examples, step-by-step processes, and screenshots to help you better understand how these features can streamline your inventory management. Stay tuned for more insights and practical tips to make the most of D365F&O’s powerful inventory tracking capabilities! That’s it for this blog. Hope this helps!! 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.
US-Based Casting Manufacturer Partners with CloudFronts for a Managed Services Agreement
We are delighted to announce that the largest US-based casting manufacturer is partnering with CloudFronts for Dynamics 365 support & maintenance with a Managed Services Agreement (MSA). The manufacturer and distributor based in Houston, Texas, is doing business since 1960 with facilities in the United States, India, and China. It was one of the first innovators to globally source municipal castings and utility fittings in the United States. They have established a reputation for providing high-quality, reliable, and cost-effective globally sourced products for the utility marketplace and OEM castings. Under this MSA, CloudFronts will provide support & maintenance services for the system based on Microsoft Dynamics 365 Sales. On this occasion, Priyesh Wagh, Dynamics 365 Practice Manager at CloudFronts, said, “We continue to work for their Dynamics 365 Sales implementation and help sales teams collaborate better. We look forward to scaling the implementation as their team continues to use the systems and derive value for growth.” “Discover How We’ve Enabled Businesses Like Yours – Explore Our Client Testimonials!” About CloudFronts CloudFronts is a Dynamics 365 focused Microsoft Solutions Partner, helping teams and organizations worldwide solve their complex business challenges with Microsoft Cloud, AI, and Azure Integration Services. We have a global presence with offices in U.S, Singapore & India. Since its inception in 2012, CloudFronts has successfully served over 200+ small and medium-sized clients all over the world, such as North America, Europe, Australia, MENA, Maldives & India, with diverse experiences in sectors ranging from Professional Services, Financial Services, Manufacturing, Retail, Pharmaceutical, Logistics/SCM, and Non-profits. Please feel free to connect with us at transform@cloudfronts.com
Data Flow with Array Filtering in Power Automate
When working with arrays in Power Automate, it’s common to need to filter or select a specific item based on certain attributes. Whether you’re handling JSON data from an API, processing records from a list, or managing dynamic content within a flow, efficiently identifying the right item is key. In this blog, we’ll explore a simple yet effective method to extract the desired item from an array using expressions in Power Automate. By the end, you’ll have a clear strategy to streamline your workflows and enhance the intelligence of your automation. In case you need to select an item from an array in Power Automate based on the value of a certain attribute, here’s how you can do it. Scenario You have an array of objects, and each object has a specific attribute. You want to efficiently select the object(s) where this attribute matches a particular value. As you see, the array of objects have different structure – All of them have an attribute called “key” and that’s the one you want to select and then process further. Let’s see how we do it. Filter Array Let’s see how you can select the item from the array based on the value of the “key” attribute instead of looping through all the items and matching. To encapsulate, by using this approach, you can efficiently select specific items from an array based on the value of a particular attribute, making your Power Automate flows more dynamic and tailored to your specific needs. 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.
Avoiding Negative Inventory: Tips and Tricks in D365 F&O
Managing inventory is an important part of any business, and keeping track of stock levels is key to smooth operations. Negative inventory happens when your system shows that you have less than zero items in stock. This can cause problems like delays, mistakes in finances, and unhappy customers. Luckily, Dynamics 365 Finance & Operations (D365F&O) has tools to help you avoid negative inventory. In this blog, we’ll share simple tips and tricks to keep your inventory accurate. What is Negative Inventory? Negative inventory means your system says you have less stock than zero. For example, if you sell or use more items than you have in storage, the system might show a negative number. This can happen because: Negative inventory can cause confusion, financial mistakes, and problems in planning and ordering stock. How to Avoid Negative Inventory in D365F&O Here are some easy steps to prevent negative inventory in D365F&O: 1. Set Up Item Model Groups Item model groups control how inventory is managed. Setting them up properly helps avoid negative inventory. 2. Use Inventory Reservations Inventory reservations make sure stock is set aside for specific orders, so you don’t overcommit. 3. Track Inventory Dimensions Inventory dimensions, like site, warehouse, batch, and serial number, help you track stock accurately. Make sure these are used correctly for each product. 4. Do Regular Cycle Counts Cycle counts help you check if the stock in your system matches what you actually have. Fixing any mistakes quickly avoids negative inventory. 5. Post Transactions in Order Posting transactions in the wrong order can cause temporary negative inventory. For example, issuing stock before recording receipts. 6. Check Inventory Transactions Often Review inventory transactions to catch and fix issues early. 7. Train Your Team Training your team is key to preventing mistakes that lead to negative inventory. Benefits of Avoiding Negative Inventory Preventing negative inventory can make a big difference for your business: Avoiding negative inventory in D365F&O is about using the right settings and following good processes. By setting up item model groups, using reservations, doing regular cycle counts, and keeping an eye on transactions, you can prevent negative inventory and keep your stock levels accurate. These steps will improve your operations, make customers happy, and help your business run smoothly. Start using these tips today to get the most out of Dynamics 365 Finance & Operations for inventory management! So, that its for this blog. Thanks for reading!! 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.
Seamlessly Importing Images via URLs in Bulk into Business Central
Whether you’re adding product catalogs or updating images for an extensive inventory, having an efficient way to bulk import images can save time and effort. In this blog post, we will walk you through the steps to import images in bulk into Business Central, providing you with a seamless method to enhance your product data. In today’s fast-paced business environment, efficiency and accuracy in managing product data are crucial for maintaining a competitive edge. Microsoft Dynamics 365 Business Central (BC) is a comprehensive enterprise resource planning (ERP) system that integrates all business functions into one platform. One of the most time-consuming tasks for businesses, especially those with large inventories, is managing and uploading product images. 1. Create a Codeunit or Processing Report: Since Business Central doesn’t have a built-in feature for bulk image import, you can create a custom codeunit or processing report to handle this task. In this example, we’ll use a codeunit. 2. Add a New Field for Image URL: Create an Item Table Extension and add a new field called “Product Image URL” to the Item table. This field will hold the URL or path for each product image. 3. Set the Image URLs Using a Configuration Package: Use a config package to set the image URLs in the “Product Image URL” field for each item. This is where you will provide the path or URL for the image associated with each product. 4. Run the Codeunit to Update Items: After populating the image URLs via the configuration package, run the codeunit in the foreground. The codeunit will process each item and update the products that have a valid URL set, linking them to the corresponding images. Below is the logic which will use the url which is set in Item master table and update all the data in bulk codeunit 50112 SetImageUrl { Permissions = tabledata Item = rimd; Description = ‘Set Image URL’; trigger OnRun() var RecItem: Record Item; Rec_Item1: Record Item; ItemPage: page “Item Card”; PictureURLDialog: Page “Picture URL Dialog”; begin Clear(RecItem); RecItem.Reset(); RecItem.SetFilter(“Product Image URL”, ‘<>%1’, ”); if RecItem.FindSet() then repeat Rec_Item1.SetRange(“No.”, RecItem.”No.”); if Rec_Item1.FindFirst() then begin PictureURLDialog.SetItemInfo(Rec_Item1.”No.”, Rec_Item1.Description, Rec_Item1.”Product Image URL”); PictureURLDialog.ImportItemPictureFromURL(); end; until RecItem.Next() = 0; end; } This approach allows you to automate the bulk import of product images into Business Central efficiently. Conclusion Importing images in bulk into Business Central can significantly enhance your operational efficiency and ensure your product records are complete and accurate. By following the steps outlined in this blog, you can easily upload and manage product images, creating a more professional and visually appealing online presence, as well as improving internal processes. Whether you’re dealing with thousands of items or just a few, these steps will guide you through the bulk image import process, saving time, reducing errors, and providing a better user experience for both your team and customers. If you need further assistance or have specific questions about your Business Central setup, feel free to reach out for personalized guidance. Happy importing! 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.
