├── .AL-Go ├── cloudDevEnv.ps1 ├── localDevEnv.ps1 └── settings.json ├── .github ├── AL-Go-Settings.json ├── RELEASENOTES.copy.md ├── Test Current.settings.json ├── Test Next Major.settings.json ├── Test Next Minor.settings.json └── workflows │ ├── AddExistingAppOrTestApp.yaml │ ├── CICD.yaml │ ├── CreateApp.yaml │ ├── CreateOnlineDevelopmentEnvironment.yaml │ ├── CreatePerformanceTestApp.yaml │ ├── CreateRelease.yaml │ ├── CreateTestApp.yaml │ ├── Current.yaml │ ├── IncrementVersionNumber.yaml │ ├── NextMajor.yaml │ ├── NextMinor.yaml │ ├── PublishToEnvironment.yaml │ ├── PullRequestHandler.yaml │ └── UpdateGitHubGoSystemFiles.yaml ├── .gitignore ├── CODEOWNERS ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── SystemAppPresentation ├── SystemAppExamples.permissionset.al ├── app.json └── src │ └── Examples │ ├── 10_SharePoint │ ├── SharePointFiles.Page.al │ ├── SharePointLists.Page.al │ ├── SharePointMgt.Codeunit.al │ ├── SharePointSetup.Page.al │ └── SharePointSetup.Table.al │ ├── 11_Password │ └── PasswordOperations.Page.al │ ├── 12_RegEx │ └── RegExExamples.Page.al │ ├── 13_AzureFunction │ ├── AzureFunctionMgt.Codeunit.al │ ├── AzureFunctionSetup.Page.al │ └── AzureFunctionSetup.Table.al │ ├── 14_Checklists │ └── ChecklistInstall.Codeunit.al │ ├── 15_Environment │ ├── CompanyInformation.PagExt.al │ └── EnvironmentInfo.Cod.al │ ├── 16_XMLWriter │ ├── CustomerList.PagExt.al │ └── XMLExport.Cod.al │ ├── 17_Base64 │ ├── Base64.Cod.al │ └── Base64.Pag.al │ ├── 18_Zip │ └── Zip.Cod.al │ ├── 19_UserSelection │ ├── Customer.TabExt.al │ └── CustomerCard.PagExt.al │ ├── 1_AzureStorage │ ├── AzureBlobStorage.Codeunit.al │ ├── AzureContainers.Page.al │ ├── AzureStorageSetup.Page.al │ ├── AzureStorageSetup.Table.al │ └── ContainerContent.Page.al │ ├── 20_Email │ ├── Email.Cod.al │ └── EmailScenario.EnumExt.al │ ├── 21_Blob │ ├── PersistentBlob.Cod.al │ └── PersistentBlob.Pag.al │ ├── 2_ImageCamera │ ├── CameraandImage.Cod.al │ ├── PurchaseHeader.TableExt.al │ ├── PurchaseOrder.PagExt.al │ └── PurchaseOrderPicture.Pag.al │ ├── 3_GeoLocation │ ├── AssetLocationMgt.Codeunit.al │ └── FixedAssetCard.PageExt.al │ ├── 4_Barcode │ ├── ItemBarcode.Report.al │ └── ItemBarcode.docx │ ├── 5_FilterTokens │ ├── FilterTokens.Codeunit.al │ └── SalesOrderList.PageExt.al │ ├── 6_RetentionPolicy │ ├── MyLog.Table.al │ └── RetenPolInstall.Codeunit.al │ ├── 7_ChoosingObjects │ ├── ChoosingObjects.Pag.al │ └── ObjectAndFields.Codeunit.al │ ├── 8_RecurranceSchedule │ ├── NextOccurrence.Pag.al │ └── OccurrenceMgt.Codeunit.al │ └── 9_Math │ └── MathOperations.Page.al └── al.code-workspace /.AL-Go/cloudDevEnv.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # Script for creating cloud development environment 3 | # Please do not modify this script as it will be auto-updated from the AL-Go Template 4 | # Recommended approach is to use as is or add a script (freddyk-devenv.ps1), which calls this script with the user specific parameters 5 | # 6 | Param( 7 | [string] $environmentName = "", 8 | [bool] $reuseExistingEnvironment, 9 | [switch] $fromVSCode 10 | ) 11 | 12 | $ErrorActionPreference = "stop" 13 | Set-StrictMode -Version 2.0 14 | 15 | try { 16 | $webClient = New-Object System.Net.WebClient 17 | $webClient.CachePolicy = New-Object System.Net.Cache.RequestCachePolicy -argumentList ([System.Net.Cache.RequestCacheLevel]::NoCacheNoStore) 18 | $webClient.Encoding = [System.Text.Encoding]::UTF8 19 | Write-Host "Downloading GitHub Helper module" 20 | $GitHubHelperPath = "$([System.IO.Path]::GetTempFileName()).psm1" 21 | $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v3.1/Github-Helper.psm1', $GitHubHelperPath) 22 | Write-Host "Downloading AL-Go Helper script" 23 | $ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1" 24 | $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v3.1/AL-Go-Helper.ps1', $ALGoHelperPath) 25 | 26 | Import-Module $GitHubHelperPath 27 | . $ALGoHelperPath -local 28 | 29 | $baseFolder = GetBaseFolder -folder $PSScriptRoot 30 | $project = GetProject -baseFolder $baseFolder -projectALGoFolder $PSScriptRoot 31 | 32 | Clear-Host 33 | Write-Host 34 | Write-Host -ForegroundColor Yellow @' 35 | _____ _ _ _____ ______ 36 | / ____| | | | | __ \ | ____| 37 | | | | | ___ _ _ __| | | | | | _____ __ |__ _ ____ __ 38 | | | | |/ _ \| | | |/ _` | | | | |/ _ \ \ / / __| | '_ \ \ / / 39 | | |____| | (_) | |_| | (_| | | |__| | __/\ V /| |____| | | \ V / 40 | \_____|_|\___/ \__,_|\__,_| |_____/ \___| \_/ |______|_| |_|\_/ 41 | 42 | '@ 43 | 44 | Write-Host @' 45 | This script will create a cloud based development environment (Business Central SaaS Sandbox) for your project. 46 | All apps and test apps will be compiled and published to the environment in the development scope. 47 | The script will also modify launch.json to have a "Cloud Sandbox ()" configuration point to your environment. 48 | 49 | '@ 50 | 51 | if (Test-Path (Join-Path $PSScriptRoot "NewBcContainer.ps1")) { 52 | Write-Host -ForegroundColor Red "WARNING: The project has a NewBcContainer override defined. Typically, this means that you cannot run a cloud development environment" 53 | } 54 | 55 | $settings = ReadSettings -baseFolder $baseFolder -project $project -userName $env:USERNAME 56 | 57 | Write-Host 58 | 59 | if (-not $environmentName) { 60 | $environmentName = Enter-Value ` 61 | -title "Environment name" ` 62 | -question "Please enter the name of the environment to create" ` 63 | -default "$($env:USERNAME)-sandbox" ` 64 | -trimCharacters @('"',"'",' ') 65 | } 66 | 67 | if ($PSBoundParameters.Keys -notcontains 'reuseExistingEnvironment') { 68 | $reuseExistingEnvironment = (Select-Value ` 69 | -title "What if the environment already exists?" ` 70 | -options @{ "Yes" = "Reuse existing environment"; "No" = "Recreate environment" } ` 71 | -question "Select behavior" ` 72 | -default "No") -eq "Yes" 73 | } 74 | 75 | CreateDevEnv ` 76 | -kind cloud ` 77 | -caller local ` 78 | -environmentName $environmentName ` 79 | -reuseExistingEnvironment:$reuseExistingEnvironment ` 80 | -baseFolder $baseFolder ` 81 | -project $project 82 | } 83 | catch { 84 | Write-Host -ForegroundColor Red "Error: $($_.Exception.Message)`nStacktrace: $($_.scriptStackTrace)" 85 | } 86 | finally { 87 | if ($fromVSCode) { 88 | Read-Host "Press ENTER to close this window" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /.AL-Go/localDevEnv.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # Script for creating local development environment 3 | # Please do not modify this script as it will be auto-updated from the AL-Go Template 4 | # Recommended approach is to use as is or add a script (freddyk-devenv.ps1), which calls this script with the user specific parameters 5 | # 6 | Param( 7 | [string] $containerName = "", 8 | [string] $auth = "", 9 | [pscredential] $credential = $null, 10 | [string] $licenseFileUrl = "", 11 | [string] $insiderSasToken = "", 12 | [switch] $fromVSCode 13 | ) 14 | 15 | $ErrorActionPreference = "stop" 16 | Set-StrictMode -Version 2.0 17 | 18 | try { 19 | $webClient = New-Object System.Net.WebClient 20 | $webClient.CachePolicy = New-Object System.Net.Cache.RequestCachePolicy -argumentList ([System.Net.Cache.RequestCacheLevel]::NoCacheNoStore) 21 | $webClient.Encoding = [System.Text.Encoding]::UTF8 22 | Write-Host "Downloading GitHub Helper module" 23 | $GitHubHelperPath = "$([System.IO.Path]::GetTempFileName()).psm1" 24 | $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v3.1/Github-Helper.psm1', $GitHubHelperPath) 25 | Write-Host "Downloading AL-Go Helper script" 26 | $ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1" 27 | $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v3.1/AL-Go-Helper.ps1', $ALGoHelperPath) 28 | 29 | Import-Module $GitHubHelperPath 30 | . $ALGoHelperPath -local 31 | 32 | $baseFolder = GetBaseFolder -folder $PSScriptRoot 33 | $project = GetProject -baseFolder $baseFolder -projectALGoFolder $PSScriptRoot 34 | 35 | Clear-Host 36 | Write-Host 37 | Write-Host -ForegroundColor Yellow @' 38 | _ _ _____ ______ 39 | | | | | | __ \ | ____| 40 | | | ___ ___ __ _| | | | | | _____ __ |__ _ ____ __ 41 | | | / _ \ / __/ _` | | | | | |/ _ \ \ / / __| | '_ \ \ / / 42 | | |____ (_) | (__ (_| | | | |__| | __/\ V /| |____| | | \ V / 43 | |______\___/ \___\__,_|_| |_____/ \___| \_/ |______|_| |_|\_/ 44 | 45 | '@ 46 | 47 | Write-Host @' 48 | This script will create a docker based local development environment for your project. 49 | 50 | NOTE: You need to have Docker installed, configured and be able to create Business Central containers for this to work. 51 | If this fails, you can setup a cloud based development environment by running cloudDevEnv.ps1 52 | 53 | All apps and test apps will be compiled and published to the environment in the development scope. 54 | The script will also modify launch.json to have a Local Sandbox configuration point to your environment. 55 | 56 | '@ 57 | 58 | $settings = ReadSettings -baseFolder $baseFolder -project $project -userName $env:USERNAME 59 | 60 | Write-Host "Checking System Requirements" 61 | $dockerProcess = (Get-Process "dockerd" -ErrorAction Ignore) 62 | if (!($dockerProcess)) { 63 | Write-Host -ForegroundColor Red "Dockerd process not found. Docker might not be started, not installed or not running Windows Containers." 64 | } 65 | if ($settings.keyVaultName) { 66 | if (-not (Get-Module -ListAvailable -Name 'Az.KeyVault')) { 67 | Write-Host -ForegroundColor Red "A keyvault name is defined in Settings, you need to have the Az.KeyVault PowerShell module installed (use Install-Module az) or you can set the keyVaultName to an empty string in the user settings file ($($ENV:UserName).settings.json)." 68 | } 69 | } 70 | 71 | Write-Host 72 | 73 | if (-not $containerName) { 74 | $containerName = Enter-Value ` 75 | -title "Container name" ` 76 | -question "Please enter the name of the container to create" ` 77 | -default "bcserver" ` 78 | -trimCharacters @('"',"'",' ') 79 | } 80 | 81 | if (-not $auth) { 82 | $auth = Select-Value ` 83 | -title "Authentication mechanism for container" ` 84 | -options @{ "Windows" = "Windows Authentication"; "UserPassword" = "Username/Password authentication" } ` 85 | -question "Select authentication mechanism for container" ` 86 | -default "UserPassword" 87 | } 88 | 89 | if (-not $credential) { 90 | if ($auth -eq "Windows") { 91 | $credential = Get-Credential -Message "Please enter your Windows Credentials" -UserName $env:USERNAME 92 | $CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName 93 | $domain = New-Object System.DirectoryServices.DirectoryEntry($CurrentDomain,$credential.UserName,$credential.GetNetworkCredential().password) 94 | if ($null -eq $domain.name) { 95 | Write-Host -ForegroundColor Red "Unable to verify your Windows Credentials, you might not be able to authenticate to your container" 96 | } 97 | } 98 | else { 99 | $credential = Get-Credential -Message "Please enter username and password for your container" -UserName "admin" 100 | } 101 | } 102 | 103 | if (-not $licenseFileUrl) { 104 | if ($settings.type -eq "AppSource App") { 105 | $description = "When developing AppSource Apps for Business Central versions prior to 22, your local development environment needs the developer licensefile with permissions to your AppSource app object IDs" 106 | $default = "none" 107 | } 108 | else { 109 | $description = "When developing PTEs, you can optionally specify a developer licensefile with permissions to object IDs of your dependant apps" 110 | $default = "none" 111 | } 112 | 113 | $licenseFileUrl = Enter-Value ` 114 | -title "LicenseFileUrl" ` 115 | -description $description ` 116 | -question "Local path or a secure download URL to license file " ` 117 | -default $default ` 118 | -doNotConvertToLower ` 119 | -trimCharacters @('"',"'",' ') 120 | } 121 | 122 | if ($licenseFileUrl -eq "none") { 123 | $licenseFileUrl = "" 124 | } 125 | 126 | CreateDevEnv ` 127 | -kind local ` 128 | -caller local ` 129 | -containerName $containerName ` 130 | -baseFolder $baseFolder ` 131 | -project $project ` 132 | -auth $auth ` 133 | -credential $credential ` 134 | -licenseFileUrl $licenseFileUrl ` 135 | -insiderSasToken $insiderSasToken 136 | } 137 | catch { 138 | Write-Host -ForegroundColor Red "Error: $($_.Exception.Message)`nStacktrace: $($_.scriptStackTrace)" 139 | } 140 | finally { 141 | if ($fromVSCode) { 142 | Read-Host "Press ENTER to close this window" 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /.AL-Go/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "country": "us", 3 | "appFolders": [], 4 | "testFolders": [], 5 | "bcptTestFolders": [] 6 | } 7 | -------------------------------------------------------------------------------- /.github/AL-Go-Settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "PTE", 3 | "templateUrl": "https://github.com/microsoft/AL-Go-PTE@main" 4 | } 5 | -------------------------------------------------------------------------------- /.github/RELEASENOTES.copy.md: -------------------------------------------------------------------------------- 1 | ## v3.1 2 | 3 | ### Issues 4 | 5 | Issue #446 Wrong NewLine character in Release Notes 6 | Issue #453 DeliverToStorage - override fails reading secrets 7 | Issue #434 Use gh auth token to get authentication token instead of gh auth status 8 | Issue #501 The Create New App action will now use 22.0.0.0 as default application reference and include NoImplicitwith feature. 9 | 10 | 11 | ### New behavior 12 | 13 | The following workflows: 14 | 15 | - Create New App 16 | - Create New Test App 17 | - Create New Performance Test App 18 | - Increment Version Number 19 | - Add Existing App 20 | - Create Online Development Environment 21 | 22 | All these actions now uses the selected branch in the **Run workflow** dialog as the target for the Pull Request or Direct COMMIT. 23 | 24 | ### New Settings 25 | 26 | - `UseCompilerFolder`: Setting useCompilerFolder to true causes your pipelines to use containerless compiling. Unless you also set `doNotPublishApps` to true, setting useCompilerFolder to true won't give you any performance advantage, since AL-Go for GitHub will still need to create a container in order to publish and test the apps. In the future, publishing and testing will be split from building and there will be other options for getting an instance of Business Central for publishing and testing. 27 | - `vsixFile`: vsixFile should be a direct download URL to the version of the AL Language extension you want to use for building the project or repo. By default, AL-Go will use the AL Language extension that comes with the Business Central Artifacts. 28 | 29 | ### New Actions 30 | 31 | - **DetermineArtifactUrl** is used to determine which artifacts to use for building a project in CI/CD, PullRequestHandler, Current, NextMinor and NextMajor workflows. 32 | 33 | ### License File 34 | 35 | With the changes to the CRONUS license in Business Central version 22, that license can in most cases be used as a developer license for AppSource Apps and it is no longer mandatory to specify a license file in AppSource App repositories. 36 | Obviously, if you build and test your app for Business Central versions prior to 21, it will fail if you don't specify a licenseFileUrl secret. 37 | 38 | ## v3.0 39 | 40 | ### **NOTE:** When upgrading to this version 41 | When upgrading to this version form earlier versions of AL-Go for GitHub, you will need to run the _Update AL-Go System Files_ workflow twice if you have the `useProjectDependencies` setting set to _true_. 42 | 43 | ### Publish to unknown environment 44 | You can now run the **Publish To Environment** workflow without creating the environment in GitHub or settings up-front, just by specifying the name of a single environment in the Environment Name when running the workflow. 45 | Subsequently, if an AuthContext secret hasn't been created for this environment, the Device Code flow authentication will be initiated from the Publish To Environment workflow and you can publish to the new environment without ever creating a secret. 46 | Open Workflow details to get the device Code for authentication in the job summary for the initialize job. 47 | 48 | ### Create Online Dev. Environment 49 | When running the **Create Online Dev. Environment** workflow without having the _adminCenterApiCredentials_ secret created, the workflow will intiate the deviceCode flow and allow you to authenticate to the Business Central Admin Center. 50 | Open Workflow details to get the device Code for authentication in the job summary for the initialize job. 51 | 52 | ### Issues 53 | - Issue #391 Create release action - CreateReleaseBranch error 54 | - Issue 434 Building local DevEnv, downloading dependencies: Authentication fails when using "gh auth status" 55 | 56 | ### Changes to Pull Request Process 57 | In v2.4 and earlier, the PullRequestHandler would trigger the CI/CD workflow to run the PR build. 58 | Now, the PullRequestHandler will perform the build and the CI/CD workflow is only run on push (or manual dispatch) and will perform a complete build. 59 | 60 | ### Build modes per project 61 | Build modes can now be specified per project 62 | 63 | ### New Actions 64 | - **DetermineProjectsToBuild** is used to determine which projects to build in PullRequestHandler, CI/CD, Current, NextMinor and NextMajor workflows. 65 | - **CalculateArtifactNames** is used to calculate artifact names in PullRequestHandler, CI/CD, Current, NextMinor and NextMajor workflows. 66 | - **VerifyPRChanges** is used to verify whether a PR contains changes, which are not allowed from a fork. 67 | 68 | ## v2.4 69 | 70 | ### Issues 71 | - Issue #171 create a workspace file when creating a project 72 | - Issue #356 Publish to AppSource fails in multi project repo 73 | - Issue #358 Publish To Environment Action stopped working in v2.3 74 | - Issue #362 Support for EnableTaskScheduler 75 | - Issue #360 Creating a release and deploying from a release branch 76 | - Issue #371 'No previous release found' for builds on release branches 77 | - Issue #376 CICD jobs that are triggered by the pull request trigger run directly to an error if title contains quotes 78 | 79 | ### Release Branches 80 | **NOTE:** Release Branches are now only named after major.minor if the patch value is 0 in the release tag (which must be semver compatible) 81 | 82 | This version contains a number of bug fixes to release branches, to ensure that the recommended branching strategy is fully supported. Bugs fixed includes: 83 | - Release branches was named after the full tag (1.0.0), even though subsequent hotfixes released from this branch would be 1.0.x 84 | - Release branches named 1.0 wasn't picked up as a release branch 85 | - Release notes contained the wrong changelog 86 | - The previous release was always set to be the first release from a release branch 87 | - SemVerStr could not have 5 segments after the dash 88 | - Release was created on the right SHA, but the release branch was created on the wrong SHA 89 | 90 | Recommended branching strategy: 91 | 92 | ![Branching Strategy](https://raw.githubusercontent.com/microsoft/AL-Go/main/Scenarios/images/branchingstrategy.png) 93 | 94 | ### New Settings 95 | New Project setting: EnableTaskScheduler in container executing tests and when setting up local development environment 96 | 97 | ### Support for GitHub variables: ALGoOrgSettings and ALGoRepoSettings 98 | Recently, GitHub added support for variables, which you can define on your organization or your repository. 99 | AL-Go now supports that you can define a GitHub variable called ALGoOrgSettings, which will work for all repositories (with access to the variable) 100 | Org Settings will be applied before Repo settings and local repository settings files will override values in the org settings 101 | You can also define a variable called ALGoRepoSettings on the repository, which will be applied after reading the Repo Settings file in the repo 102 | Example for usage could be setup of branching strategies, versioning or an appDependencyProbingPaths to repositories which all repositories share. 103 | appDependencyProbingPaths from settings variables are merged together with appDependencyProbingPaths defined in repositories 104 | 105 | ### Refactoring and tests 106 | ReadSettings has been refactored to allow organization wide settings to be added as well. CI Tests have been added to cover ReadSettings. 107 | 108 | ## v2.3 109 | 110 | ### Issues 111 | - Issue #312 Branching enhancements 112 | - Issue #229 Create Release action tags wrong commit 113 | - Issue #283 Create Release workflow uses deprecated actions 114 | - Issue #319 Support for AssignPremiumPlan 115 | - Issue #328 Allow multiple projects in AppSource App repo 116 | - Issue #344 Deliver To AppSource on finding app.json for the app 117 | - Issue #345 LocalDevEnv.ps1 can't Dowload the file license file 118 | 119 | ### New Settings 120 | New Project setting: AssignPremiumPlan on user in container executing tests and when setting up local development environment 121 | New Repo setting: unusedALGoSystemFiles is an array of AL-Go System Files, which won't be updated during Update AL-Go System Files. They will instead be removed. Use with care, as this can break the AL-Go for GitHub functionality and potentially leave your repo no longer functional. 122 | 123 | ### Build modes support 124 | AL-Go projects can now be built in different modes, by specifying the _buildModes_ setting in AL-Go-Settings.json. Read more about build modes in the [Basic Repository settings](https://github.com/microsoft/AL-Go/blob/main/Scenarios/settings.md#basic-repository-settings). 125 | 126 | ### LocalDevEnv / CloudDevEnv 127 | With the support for PowerShell 7 in BcContainerHelper, the scripts LocalDevEnv and CloudDevEnv (placed in the .AL-Go folder) for creating development environments have been modified to run inside VS Code instead of spawning a new powershell 5.1 session. 128 | 129 | ### Continuous Delivery 130 | Continuous Delivery can now run from other branches than main. By specifying a property called branches, containing an array of branches in the deliveryContext json construct, the artifacts generated from this branch are also delivered. The branch specification can include wildcards (like release/*). Default is main, i.e. no changes to functionality. 131 | 132 | ### Continuous Deployment 133 | Continuous Deployment can now run from other branches than main. By creating a repo setting (.github/AL-Go-Settings.json) called **`-Branches`**, which is an array of branches, which will deploy the generated artifacts to this environment. The branch specification can include wildcards (like release/*), although this probably won't be used a lot in continuous deployment. Default is main, i.e. no changes to functionality. 134 | 135 | ### Create Release 136 | When locating artifacts for the various projects, the SHA used to build the artifact is used for the release tag 137 | If all projects are not available with the same SHA, this error is thrown: **The build selected for release doesn't contain all projects. Please rebuild all projects by manually running the CI/CD workflow and recreate the release.** 138 | There is no longer a hard dependency on the main branch name from Create Release. 139 | 140 | ### AL-Go Tests 141 | Some unit tests have been added and AL-Go unit tests can now be run directly from VS Code. 142 | Another set of end to end tests have also been added and in the documentation on contributing to AL-Go, you can see how to run these in a local fork or from VS Code. 143 | 144 | ### LF, UTF8 and JSON 145 | GitHub natively uses LF as line seperator in source files. 146 | In earlier versions of AL-Go for GitHub, many scripts and actions would use CRLF and convert back and forth. Some files were written with UTF8 BOM (Byte Order Mark), other files without and JSON formatting was done using PowerShell 5.1 (which is different from PowerShell 7). 147 | In the latest version, we always use LF as line seperator, UTF8 without BOM and JSON files are written using PowerShell 7. If you have self-hosted runners, you need to ensure that PS7 is installed to make this work. 148 | 149 | ### Experimental Support 150 | Setting the repo setting "shell" to "pwsh", followed by running Update AL-Go System Files, will cause all PowerShell code to be run using PowerShell 7 instead of PowerShell 5. This functionality is experimental. Please report any issues at https://github.com/microsoft/AL-Go/issues 151 | Setting the repo setting "runs-on" to "Ubuntu-Latest", followed by running Update AL-Go System Files, will cause all non-build jobs to run using Linux. This functionality is experimental. Please report any issues at https://github.com/microsoft/AL-Go/issues 152 | 153 | ## v2.2 154 | 155 | ### Enhancements 156 | - Container Event log is added as a build artifact if builds or tests are failing 157 | 158 | ### Issues 159 | - Issue #280 Overflow error when test result summary was too big 160 | - Issue #282, 292 AL-Go for GitHub causes GitHub to issue warnings 161 | - Issue #273 Potential security issue in Pull Request Handler in Open Source repositories 162 | - Issue #303 PullRequestHandler fails on added files 163 | - Issue #299 Multi-project repositories build all projects on Pull Requests 164 | - Issue #291 Issues with new Pull Request Handler 165 | - Issue #287 AL-Go pipeline fails in ReadSettings step 166 | 167 | ### Changes 168 | - VersioningStrategy 1 is no longer supported. GITHUB_ID has changed behavior (Issue #277) 169 | 170 | ## v2.1 171 | 172 | ### Issues 173 | - Issue #233 AL-Go for GitHub causes GitHub to issue warnings 174 | - Issue #244 Give error if AZURE_CREDENTIALS contains line breaks 175 | 176 | ### Changes 177 | - New workflow: PullRequestHandler to handle all Pull Requests and pass control safely to CI/CD 178 | - Changes to yaml files, PowerShell scripts and codeowners files are not permitted from fork Pull Requests 179 | - Test Results summary (and failed tests) are now displayed directly in the CI/CD workflow and in the Pull Request Check 180 | 181 | ### Continuous Delivery 182 | - Proof Of Concept Delivery to GitHub Packages and Nuget 183 | 184 | ## v2.0 185 | 186 | ### Issues 187 | - Issue #143 Commit Message for **Increment Version Number** workflow 188 | - Issue #160 Create local DevEnv aith appDependencyProbingPaths 189 | - Issue #156 Versioningstrategy 2 doesn't use 24h format 190 | - Issue #155 Initial Add existing app fails with "Cannot find path" 191 | - Issue #152 Error when loading dependencies from releases 192 | - Issue #168 Regression in preview fixed 193 | - Issue #189 Warnings: Resource not accessible by integration 194 | - Issue #190 PublishToEnvironment is not working with AL-Go-PTE@preview 195 | - Issue #186 AL-GO build fails for multi-project repository when there's nothing to build 196 | - When you have GitHub pages enabled, AL-Go for GitHub would try to publish to github_pages environment 197 | - Special characters wasn't supported in parameters to GitHub actions (Create New App etc.) 198 | 199 | ### Continuous Delivery 200 | - Added new GitHub Action "Deliver" to deliver build output to Storage or AppSource 201 | - Refactor CI/CD and Release workflows to use new deliver action 202 | - Custom delivery supported by creating scripts with the naming convention DeliverTo*.ps1 in the .github folder 203 | 204 | ### AppSource Apps 205 | - New workflow: Publish to AppSource 206 | - Continuous Delivery to AppSource validation supported 207 | 208 | ### Settings 209 | - New Repo setting: CICDPushBranches can be specified as an array of branches, which triggers a CI/CD workflow on commit. Default is main', release/\*, feature/\* 210 | - New Repo setting: CICDPullRequestBranches can be specified as an array of branches, which triggers a CI/CD workflow on pull request. Default is main 211 | - New Repo setting: CICDSchedule can specify a CRONTab on when you want to run CI/CD on a schedule. Note that this will disable Push and Pull Request triggers unless specified specifically using CICDPushBranches or CICDPullRequestBranches 212 | - New Repo setting: UpdateGitHubGoSystemFilesSchedule can specify a CRONTab on when you want to Update AL-Go System Files. Note that when running on a schedule, update AL-Go system files will perfom a direct commit and not create a pull request. 213 | - New project Setting: AppSourceContext should be a compressed json structure containing authContext for submitting to AppSource. The BcContainerHelperFunction New-ALGoAppSourceContext will help you create this structure. 214 | - New project Setting: AppSourceContinuousDelivery. Set this to true in enable continuous delivery for this project to AppSource. This requires AppSourceContext and AppSourceProductId to be set as well 215 | - New project Setting: AppSourceProductId should be set to the product Id of this project in AppSource 216 | - New project Setting: AppSourceMainAppFolder. If you have multiple appFolders, this is the folder name of the main app to submit to AppSource. 217 | 218 | ### All workflows 219 | - Support 2 folder levels projects (apps\w1, apps\dk etc.) 220 | - Better error messages for if an error occurs within an action 221 | - Special characters are now supported in secrets 222 | - Initial support for agents running inside containers on a host 223 | - Optimized workflows to have fewer jobs 224 | 225 | ### Update AL-Go System Files Workflow 226 | - workflow now displays the currently used template URL when selecting the Run Workflow action 227 | 228 | ### CI/CD workflow 229 | - Better detection of changed projects 230 | - appDependencyProbingPaths did not support multiple projects in the same repository for latestBuild dependencies 231 | - appDependencyProbingPaths with release=latestBuild only considered the last 30 artifacts 232 | - Use mutex around ReadSecrets to ensure that multiple agents on the same host doesn't clash 233 | - Add lfs when checking out files for CI/CD to support checking in dependencies 234 | - Continue on error with Deploy and Deliver 235 | 236 | ### CI/CD and Publish To New Environment 237 | - Base functionality for selecting a specific GitHub runner for an environment 238 | - Include dependencies artifacts when deploying (if generateDependencyArtifacts is true) 239 | 240 | ### localDevEnv.ps1 and cloudDevEnv.ps1 241 | - Display clear error message if something goes wrong 242 | 243 | ## v1.5 244 | 245 | ### Issues 246 | - Issue #100 - Add more resilience to localDevEnv.ps1 and cloudDevEnv.ps1 247 | - Issue #131 - Special characters are not allowed in secrets 248 | 249 | ### All workflows 250 | - During initialize, all AL-Go settings files are now checked for validity and reported correctly 251 | - During initialize, the version number of AL-Go for GitHub is printed in large letters (incl. preview or dev.) 252 | 253 | ### New workflow: Create new Performance Test App 254 | - Create BCPT Test app and add to bcptTestFolders to run bcpt Tests in workflows (set doNotRunBcptTests in workflow settings for workflows where you do NOT want this) 255 | 256 | ### Update AL-Go System Files Workflow 257 | - Include release notes of new version in the description of the PR (and in the workflow output) 258 | 259 | ### CI/CD workflow 260 | - Apps are not signed when the workflow is running as a Pull Request validation 261 | - if a secret called applicationInsightsConnectionString exists, then the value of that will be used as ApplicationInsightsConnectionString for the app 262 | 263 | ### Increment Version Number Workflow 264 | - Bugfix: increment all apps using f.ex. +0.1 would fail. 265 | 266 | ### Environments 267 | - Add suport for EnvironmentName redirection by adding an Environment Secret under the environment or a repo secret called \_EnvironmentName with the actual environment name. 268 | - No default environment name on Publish To Environment 269 | - For multi-project repositories, you can specify an environment secret called Projects or a repo setting called \_Projects, containing the projects you want to deploy to this environment. 270 | 271 | ### Settings 272 | - New setting: **runs-on** to allow modifying runs-on for all jobs (requires Update AL-Go System files after changing the setting) 273 | - New setting: **DoNotSignApps** - setting this to true causes signing of the app to be skipped 274 | - New setting: **DoNotPublishApps** - setting this to true causes the workflow to skip publishing, upgrading and testing the app to improve performance. 275 | - New setting: **ConditionalSettings** to allow to use different settings for specific branches. Example: 276 | ``` 277 | "ConditionalSettings": [ 278 | { 279 | "branches": [ 280 | "feature/*" 281 | ], 282 | "settings": { 283 | "doNotPublishApps": true, 284 | "doNotSignApps": true 285 | } 286 | } 287 | ] 288 | ``` 289 | - Default **BcContainerHelperVersion** is now based on AL-Go version. Preview AL-Go selects preview bcContainerHelper, normal selects latest. 290 | - New Setting: **bcptTestFolders** contains folders with BCPT tests, which will run in all build workflows 291 | - New Setting: set **doNotRunBcptTest** to true (in workflow specific settings file?) to avoid running BCPT tests 292 | - New Setting: set **obsoleteTagMinAllowedMajorMinor** to enable appsource cop to validate your app against future changes (AS0105). This setting will become auto-calculated in Test Current, Test Next Minor and Test Next Major later. 293 | 294 | ## v1.4 295 | 296 | ### All workflows 297 | - Add requested permissions to avoid dependency on user/org defaults being too permissive 298 | 299 | ### Update AL-Go System Files Workflow 300 | - Default host to https://github.com/ (you can enter **myaccount/AL-Go-PTE@main** to change template) 301 | - Support for "just" changing branch (ex. **\@Preview**) to shift to the preview version 302 | 303 | ### CI/CD Workflow 304 | - Support for feature branches (naming **feature/\***) - CI/CD workflow will run, but not generate artifacts nor deploy to QA 305 | 306 | ### Create Release Workflow 307 | - Support for release branches 308 | - Force Semver format on release tags 309 | - Add support for creating release branches on release (naming release/\*) 310 | - Add support for incrementing main branch after release 311 | 312 | ### Increment version number workflow 313 | - Add support for incremental (and absolute) version number change 314 | 315 | ### Environments 316 | - Support environmentName redirection in CI/CD and Publish To Environments workflows 317 | - If the name in Environments or environments settings doesn't match the actual environment name, 318 | - You can add a secret called EnvironmentName under the environment (or \_ENVIRONMENTNAME globally) 319 | 320 | 321 | ## v1.3 322 | 323 | ### Issues 324 | - Issue #90 - Environments did not work. Secrets for environments specified in settings can now be **\_AUTHCONTEXT** 325 | 326 | ### CI/CD Workflow 327 | - Give warning instead of error If no artifacts are found in **appDependencyProbingPaths** 328 | 329 | ## v1.2 330 | 331 | ### Issues 332 | - Issue #90 - Environments did not work. Environments (even if only defined in the settings file) did not work for private repositories if you didn't have a premium subscription. 333 | 334 | ### Local scripts 335 | - **LocalDevEnv.ps1** and ***CloudDevEnv.ps1** will now spawn a new PowerShell window as admin instead of running inside VS Code. Normally people doesn't run VS Code as administrator, and they shouldn't have to. Furthermore, I have seen a some people having problems when running these scripts inside VS Code. 336 | 337 | 338 | ## v1.1 339 | 340 | ### Settings 341 | - New Repo Setting: **GenerateDependencyArtifact** (default **false**). When true, CI/CD pipeline generates an artifact with the external dependencies used for building the apps in this repo. 342 | - New Repo Setting: **UpdateDependencies** (default **false**). When true, the default artifact for building the apps in this repo is not the latest available artifacts for this country, but instead the first compatible version (after calculating application dependencies). It is recommended to run Test Current, Test NextMinor and Test NextMajor in order to test your app against current and future builds. 343 | 344 | ### CI/CD Workflow 345 | - New Artifact: BuildOutput.txt. All compiler warnings and errors are emitted to this file to make it easier to investigate compiler errors and build a better UI for build errors and test results going forward. 346 | - TestResults artifact name to include repo version number and workflow name (for Current, NextMinor and NextMajor) 347 | - Default dependency version in appDependencyProbingPaths setting used is now latest Release instead of LatestBuild 348 | -------------------------------------------------------------------------------- /.github/Test Current.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "artifact": "////latest", 3 | "cacheImageName": "", 4 | "versioningStrategy": 15 5 | } 6 | -------------------------------------------------------------------------------- /.github/Test Next Major.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "artifact": "////nextmajor/{INSIDERSASTOKEN}", 3 | "cacheImageName": "", 4 | "versioningStrategy": 15 5 | } 6 | -------------------------------------------------------------------------------- /.github/Test Next Minor.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "artifact": "////nextminor/{INSIDERSASTOKEN}", 3 | "cacheImageName": "", 4 | "versioningStrategy": 15 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/AddExistingAppOrTestApp.yaml: -------------------------------------------------------------------------------- 1 | name: 'Add existing app or test app' 2 | 3 | run-name: "Add existing app or test app in [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | project: 9 | description: Project name if the repository is setup for multiple projects 10 | required: false 11 | default: '.' 12 | url: 13 | description: Direct Download Url of .app or .zip file 14 | required: true 15 | directCommit: 16 | description: Direct COMMIT (Y/N) 17 | required: false 18 | default: 'N' 19 | 20 | permissions: 21 | contents: write 22 | pull-requests: write 23 | 24 | defaults: 25 | run: 26 | shell: powershell 27 | 28 | env: 29 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 30 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 31 | 32 | jobs: 33 | AddExistingAppOrTestApp: 34 | runs-on: [ windows-latest ] 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v3 38 | 39 | - name: Initialize the workflow 40 | id: init 41 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 42 | with: 43 | shell: powershell 44 | eventId: "DO0090" 45 | 46 | - name: Add existing app 47 | uses: microsoft/AL-Go-Actions/AddExistingApp@v3.1 48 | with: 49 | shell: powershell 50 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 51 | project: ${{ github.event.inputs.project }} 52 | url: ${{ github.event.inputs.url }} 53 | directCommit: ${{ github.event.inputs.directCommit }} 54 | 55 | - name: Finalize the workflow 56 | if: always() 57 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 58 | with: 59 | shell: powershell 60 | eventId: "DO0090" 61 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 62 | -------------------------------------------------------------------------------- /.github/workflows/CreateApp.yaml: -------------------------------------------------------------------------------- 1 | name: 'Create a new app' 2 | 3 | run-name: "Create a new app in [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | project: 9 | description: Project name if the repository is setup for multiple projects 10 | required: false 11 | default: '.' 12 | name: 13 | description: Name 14 | required: true 15 | publisher: 16 | description: Publisher 17 | required: true 18 | idrange: 19 | description: ID range (from..to) 20 | required: true 21 | sampleCode: 22 | description: Include Sample code (Y/N) 23 | required: false 24 | default: 'Y' 25 | directCommit: 26 | description: Direct COMMIT (Y/N) 27 | required: false 28 | default: "N" 29 | 30 | permissions: 31 | contents: write 32 | pull-requests: write 33 | 34 | defaults: 35 | run: 36 | shell: powershell 37 | 38 | env: 39 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 40 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 41 | 42 | jobs: 43 | CreateApp: 44 | runs-on: [ windows-latest ] 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v3 48 | 49 | - name: Initialize the workflow 50 | id: init 51 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 52 | with: 53 | shell: powershell 54 | eventId: "DO0092" 55 | 56 | - name: Read settings 57 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 58 | with: 59 | shell: powershell 60 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 61 | get: type 62 | 63 | - name: Creating a new app 64 | uses: microsoft/AL-Go-Actions/CreateApp@v3.1 65 | with: 66 | shell: powershell 67 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 68 | project: ${{ github.event.inputs.project }} 69 | type: ${{ env.type }} 70 | name: ${{ github.event.inputs.name }} 71 | publisher: ${{ github.event.inputs.publisher }} 72 | idrange: ${{ github.event.inputs.idrange }} 73 | sampleCode: ${{ github.event.inputs.sampleCode }} 74 | directCommit: ${{ github.event.inputs.directCommit }} 75 | 76 | - name: Finalize the workflow 77 | if: always() 78 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 79 | with: 80 | shell: powershell 81 | eventId: "DO0092" 82 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 83 | -------------------------------------------------------------------------------- /.github/workflows/CreateOnlineDevelopmentEnvironment.yaml: -------------------------------------------------------------------------------- 1 | name: ' Create Online Dev. Environment' 2 | 3 | run-name: "Create Online Dev. Environment for [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | environmentName: 9 | description: Name of the online environment 10 | required: true 11 | reUseExistingEnvironment: 12 | description: Reuse environment if it exists 13 | required: false 14 | default: 'N' 15 | directCommit: 16 | description: Direct COMMIT (Y/N) 17 | required: false 18 | default: 'N' 19 | 20 | permissions: 21 | contents: write 22 | pull-requests: write 23 | 24 | defaults: 25 | run: 26 | shell: powershell 27 | 28 | env: 29 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 30 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 31 | 32 | jobs: 33 | Initialize: 34 | runs-on: [ windows-latest ] 35 | outputs: 36 | deviceCode: ${{ steps.authenticate.outputs.deviceCode }} 37 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v3 41 | 42 | - name: Initialize the workflow 43 | id: init 44 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 45 | with: 46 | shell: powershell 47 | eventId: "DO0093" 48 | 49 | - name: Read settings 50 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 51 | with: 52 | shell: powershell 53 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 54 | 55 | - name: Read secrets 56 | uses: microsoft/AL-Go-Actions/ReadSecrets@v3.1 57 | env: 58 | secrets: ${{ toJson(secrets) }} 59 | with: 60 | shell: powershell 61 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 62 | settingsJson: ${{ env.Settings }} 63 | secrets: 'adminCenterApiCredentials' 64 | 65 | - name: Check AdminCenterApiCredentials / Initiate Device Login (open to see code) 66 | id: authenticate 67 | run: | 68 | $ErrorActionPreference = "STOP" 69 | Set-StrictMode -version 2.0 70 | $adminCenterApiCredentials = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($env:adminCenterApiCredentials)) 71 | if ($adminCenterApiCredentials) { 72 | Write-Host "AdminCenterApiCredentials provided in secret $($ENV:adminCenterApiCredentialsSecretName)!" 73 | Set-Content -Path $ENV:GITHUB_STEP_SUMMARY -value "Admin Center Api Credentials was provided in a secret called $($ENV:adminCenterApiCredentialsSecretName). Using this information for authentication." 74 | } 75 | else { 76 | Write-Host "AdminCenterApiCredentials not provided, initiating Device Code flow" 77 | $ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1" 78 | $webClient = New-Object System.Net.WebClient 79 | $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v3.1/AL-Go-Helper.ps1', $ALGoHelperPath) 80 | . $ALGoHelperPath 81 | $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE 82 | $authContext = New-BcAuthContext -includeDeviceLogin -deviceLoginTimeout ([TimeSpan]::FromSeconds(0)) 83 | CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath 84 | Set-Content -Path $ENV:GITHUB_STEP_SUMMARY -value "AL-Go needs access to the Business Central Admin Center Api and could not locate a secret called $($ENV:adminCenterApiCredentialsSecretName) (https://aka.ms/ALGoSettings#AdminCenterApiCredentialsSecretName)`n`n$($authContext.message)" 85 | Add-Content -Path $env:GITHUB_OUTPUT -Value "deviceCode=$($authContext.deviceCode)" 86 | } 87 | 88 | CreateDevelopmentEnvironment: 89 | runs-on: [ windows-latest ] 90 | name: Create Development Environment 91 | needs: [ Initialize ] 92 | env: 93 | deviceCode: ${{ needs.Initialize.outputs.deviceCode }} 94 | steps: 95 | - name: Checkout 96 | uses: actions/checkout@v3 97 | 98 | - name: Read settings 99 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 100 | with: 101 | shell: powershell 102 | parentTelemetryScopeJson: ${{ needs.Initialize.outputs.telemetryScopeJson }} 103 | 104 | - name: Read secrets 105 | uses: microsoft/AL-Go-Actions/ReadSecrets@v3.1 106 | env: 107 | secrets: ${{ toJson(secrets) }} 108 | with: 109 | shell: powershell 110 | parentTelemetryScopeJson: ${{ needs.Initialize.outputs.telemetryScopeJson }} 111 | settingsJson: ${{ env.Settings }} 112 | secrets: 'adminCenterApiCredentials' 113 | 114 | - name: Set AdminCenterApiCredentials 115 | run: | 116 | if ($env:deviceCode) { 117 | $adminCenterApiCredentials = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("{""deviceCode"":""$($env:deviceCode)""}")) 118 | Add-Content -path $env:GITHUB_ENV -value "adminCenterApiCredentials=$adminCenterApiCredentials" 119 | } 120 | 121 | - name: Create Development Environment 122 | uses: microsoft/AL-Go-Actions/CreateDevelopmentEnvironment@v3.1 123 | with: 124 | shell: powershell 125 | parentTelemetryScopeJson: ${{ needs.Initialize.outputs.telemetryScopeJson }} 126 | environmentName: ${{ github.event.inputs.environmentName }} 127 | reUseExistingEnvironment: ${{ github.event.inputs.reUseExistingEnvironment }} 128 | directCommit: ${{ github.event.inputs.directCommit }} 129 | adminCenterApiCredentials: ${{ env.adminCenterApiCredentials }} 130 | 131 | - name: Finalize the workflow 132 | if: always() 133 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 134 | with: 135 | shell: powershell 136 | eventId: "DO0093" 137 | telemetryScopeJson: ${{ needs.Initialize.outputs.telemetryScopeJson }} 138 | -------------------------------------------------------------------------------- /.github/workflows/CreatePerformanceTestApp.yaml: -------------------------------------------------------------------------------- 1 | name: 'Create a new performance test app' 2 | 3 | run-name: "Create a new performance test app in [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | project: 9 | description: Project name if the repository is setup for multiple projects 10 | required: false 11 | default: '.' 12 | name: 13 | description: Name 14 | required: true 15 | default: '.PerformanceTest' 16 | publisher: 17 | description: Publisher 18 | required: true 19 | idrange: 20 | description: ID range 21 | required: true 22 | default: '50000..99999' 23 | sampleCode: 24 | description: Include Sample code (Y/N) 25 | required: false 26 | default: 'Y' 27 | sampleSuite: 28 | description: Include Sample BCPT Suite (Y/N) 29 | required: false 30 | default: 'Y' 31 | directCommit: 32 | description: Direct COMMIT (Y/N) 33 | required: false 34 | default: 'N' 35 | 36 | permissions: 37 | contents: write 38 | pull-requests: write 39 | 40 | defaults: 41 | run: 42 | shell: powershell 43 | 44 | env: 45 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 46 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 47 | 48 | jobs: 49 | CreatePerformanceTestApp: 50 | runs-on: [ windows-latest ] 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v3 54 | 55 | - name: Initialize the workflow 56 | id: init 57 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 58 | with: 59 | shell: powershell 60 | eventId: "DO0102" 61 | 62 | - name: Creating a new test app 63 | uses: microsoft/AL-Go-Actions/CreateApp@v3.1 64 | with: 65 | shell: powershell 66 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 67 | project: ${{ github.event.inputs.project }} 68 | type: 'Performance Test App' 69 | name: ${{ github.event.inputs.name }} 70 | publisher: ${{ github.event.inputs.publisher }} 71 | idrange: ${{ github.event.inputs.idrange }} 72 | sampleCode: ${{ github.event.inputs.sampleCode }} 73 | sampleSuite: ${{ github.event.inputs.sampleSuite }} 74 | directCommit: ${{ github.event.inputs.directCommit }} 75 | 76 | - name: Finalize the workflow 77 | if: always() 78 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 79 | with: 80 | shell: powershell 81 | eventId: "DO0102" 82 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 83 | -------------------------------------------------------------------------------- /.github/workflows/CreateRelease.yaml: -------------------------------------------------------------------------------- 1 | name: ' Create release' 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | appVersion: 7 | description: App version to promote to release (default is latest) 8 | required: false 9 | default: 'latest' 10 | name: 11 | description: Name of this release 12 | required: true 13 | default: '' 14 | tag: 15 | description: Tag of this release (needs to be semantic version string https://semver.org, ex. 1.0.0) 16 | required: true 17 | default: '' 18 | prerelease: 19 | description: Prerelease (Y/N) 20 | required: false 21 | default: 'N' 22 | draft: 23 | description: Draft (Y/N) 24 | required: false 25 | default: 'N' 26 | createReleaseBranch: 27 | description: Create Release Branch (Y/N) 28 | required: false 29 | default: 'N' 30 | updateVersionNumber: 31 | description: New Version Number in main branch. Use Major.Minor for absolute change, use +Major.Minor for incremental change. 32 | required: false 33 | default: '' 34 | directCommit: 35 | description: Direct COMMIT (Y/N) 36 | required: false 37 | default: 'N' 38 | 39 | permissions: 40 | contents: write 41 | pull-requests: write 42 | actions: read 43 | 44 | concurrency: release 45 | 46 | defaults: 47 | run: 48 | shell: powershell 49 | 50 | env: 51 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 52 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 53 | 54 | jobs: 55 | CreateRelease: 56 | runs-on: [ windows-latest ] 57 | outputs: 58 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 59 | artifacts: ${{ steps.analyzeartifacts.outputs.artifacts }} 60 | releaseId: ${{ steps.createrelease.outputs.releaseId }} 61 | commitish: ${{ steps.analyzeartifacts.outputs.commitish }} 62 | releaseBranch: ${{ steps.createreleasenotes.outputs.releaseBranch }} 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@v3 66 | 67 | - name: Initialize the workflow 68 | id: init 69 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 70 | with: 71 | shell: powershell 72 | eventId: "DO0094" 73 | 74 | - name: Read settings 75 | id: ReadSettings 76 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 77 | with: 78 | shell: powershell 79 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 80 | get: templateUrl,repoName 81 | 82 | - name: Determine Projects 83 | id: determineProjects 84 | uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v3.1 85 | with: 86 | shell: powershell 87 | 88 | - name: Check for updates to AL-Go system files 89 | uses: microsoft/AL-Go-Actions/CheckForUpdates@v3.1 90 | with: 91 | shell: powershell 92 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 93 | templateUrl: ${{ env.templateUrl }} 94 | 95 | - name: Analyze Artifacts 96 | id: analyzeartifacts 97 | run: | 98 | $ErrorActionPreference = "STOP" 99 | Set-StrictMode -version 2.0 100 | $projects = '${{ steps.determineProjects.outputs.ProjectsJson }}' | ConvertFrom-Json 101 | Write-Host "projects:" 102 | $projects | ForEach-Object { Write-Host "- $_" } 103 | $include = @() 104 | $sha = '' 105 | $allArtifacts = @() 106 | $page = 1 107 | $headers = @{ 108 | "Authorization" = "token ${{ github.token }}" 109 | "Accept" = "application/json" 110 | } 111 | do { 112 | $repoArtifacts = Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri "$($ENV:GITHUB_API_URL)/repos/$($ENV:GITHUB_REPOSITORY)/actions/artifacts?per_page=100&page=$page" | ConvertFrom-Json 113 | $allArtifacts += $repoArtifacts.Artifacts 114 | $page++ 115 | } 116 | while ($repoArtifacts.Artifacts.Count -gt 0) 117 | Write-Host "Repo Artifacts count: $($repoArtifacts.total_count)" 118 | Write-Host "Downloaded Artifacts count: $($allArtifacts.Count)" 119 | $projects | ForEach-Object { 120 | $thisProject = $_ 121 | if ($thisProject -and ($thisProject -ne '.')) { 122 | $project = $thisProject.Replace('\','_').Replace('/','_') 123 | } 124 | else { 125 | $project = $env:repoName 126 | } 127 | $refname = "$ENV:GITHUB_REF_NAME".Replace('/','_') 128 | Write-Host "Analyzing artifacts for project $project" 129 | $appVersion = '${{ github.event.inputs.appVersion }}' 130 | if ($appVersion -eq "latest") { 131 | Write-Host "Grab latest" 132 | $artifact = $allArtifacts | Where-Object { $_.name -like "$project-$refname-Apps-*" } | Select-Object -First 1 133 | } 134 | else { 135 | Write-Host "Search for $project-$refname-Apps-$appVersion" 136 | $artifact = $allArtifacts | Where-Object { $_.name -eq "$project-$refname-Apps-$appVersion" } | Select-Object -First 1 137 | } 138 | if ($artifact) { 139 | $artifactsVersion = $artifact.name.SubString($artifact.name.LastIndexOf('-Apps-')+6) 140 | } 141 | else { 142 | Write-Host "::Error::No artifacts found for this project" 143 | exit 1 144 | } 145 | if ($sha) { 146 | if ($artifact.workflow_run.head_sha -ne $sha) { 147 | Write-Host "::Error::The build selected for release doesn't contain all projects. Please rebuild all projects by manually running the CI/CD workflow and recreate the release." 148 | throw "The build selected for release doesn't contain all projects. Please rebuild all projects by manually running the CI/CD workflow and recreate the release." 149 | } 150 | } 151 | else { 152 | $sha = $artifact.workflow_run.head_sha 153 | } 154 | 155 | $allArtifacts | Where-Object { ($_.name -like "$project-$refname-Apps-$($artifactsVersion)" -or $_.name -like "$project-$refname-TestApps-$($artifactsVersion)" -or $_.name -like "$project-$refname-Dependencies-$($artifactsVersion)") } | ForEach-Object { 156 | $atype = $_.name.SubString(0,$_.name.Length-$artifactsVersion.Length-1) 157 | $atype = $atype.SubString($atype.LastIndexOf('-')+1) 158 | $include += $( [ordered]@{ "name" = $_.name; "url" = $_.archive_download_url; "atype" = $atype; "project" = $thisproject } ) 159 | } 160 | if ($include.Count -eq 0) { 161 | Write-Host "::Error::No artifacts found" 162 | exit 1 163 | } 164 | } 165 | $artifacts = @{ "include" = $include } 166 | $artifactsJson = $artifacts | ConvertTo-Json -compress 167 | Add-Content -Path $env:GITHUB_OUTPUT -Value "artifacts=$artifactsJson" 168 | Write-Host "artifacts=$artifactsJson" 169 | Add-Content -Path $env:GITHUB_OUTPUT -Value "commitish=$sha" 170 | Write-Host "commitish=$sha" 171 | 172 | - name: Prepare release notes 173 | id: createreleasenotes 174 | uses: microsoft/AL-Go-Actions/CreateReleaseNotes@v3.1 175 | with: 176 | shell: powershell 177 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 178 | tag_name: ${{ github.event.inputs.tag }} 179 | 180 | - name: Create release 181 | uses: actions/github-script@v6 182 | id: createrelease 183 | env: 184 | bodyMD: ${{ steps.createreleasenotes.outputs.releaseNotes }} 185 | with: 186 | github-token: ${{ secrets.GITHUB_TOKEN }} 187 | script: | 188 | var bodyMD = process.env.bodyMD 189 | const createReleaseResponse = await github.rest.repos.createRelease({ 190 | owner: context.repo.owner, 191 | repo: context.repo.repo, 192 | tag_name: '${{ github.event.inputs.tag }}', 193 | name: '${{ github.event.inputs.name }}', 194 | body: bodyMD.replaceAll('\\n','\n').replaceAll('%0A','\n').replaceAll('%0D','\n').replaceAll('%25','%'), 195 | draft: ${{ github.event.inputs.draft=='Y' }}, 196 | prerelease: ${{ github.event.inputs.prerelease=='Y' }}, 197 | make_latest: 'legacy', 198 | target_commitish: '${{ steps.analyzeartifacts.outputs.commitish }}' 199 | }); 200 | const { 201 | data: { id: releaseId, html_url: htmlUrl, upload_url: uploadUrl } 202 | } = createReleaseResponse; 203 | core.setOutput('releaseId', releaseId); 204 | 205 | UploadArtifacts: 206 | runs-on: [ windows-latest ] 207 | needs: [ CreateRelease ] 208 | strategy: 209 | matrix: ${{ fromJson(needs.CreateRelease.outputs.artifacts) }} 210 | fail-fast: true 211 | steps: 212 | - name: Checkout 213 | uses: actions/checkout@v3 214 | 215 | - name: Read settings 216 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 217 | with: 218 | shell: powershell 219 | parentTelemetryScopeJson: ${{ needs.CreateRelease.outputs.telemetryScopeJson }} 220 | 221 | - name: Read secrets 222 | uses: microsoft/AL-Go-Actions/ReadSecrets@v3.1 223 | env: 224 | secrets: ${{ toJson(secrets) }} 225 | with: 226 | shell: powershell 227 | parentTelemetryScopeJson: ${{ needs.CreateRelease.outputs.telemetryScopeJson }} 228 | settingsJson: ${{ env.Settings }} 229 | secrets: 'nuGetContext,storageContext' 230 | 231 | - name: Download artifact 232 | run: | 233 | $ErrorActionPreference = "STOP" 234 | Set-StrictMode -version 2.0 235 | Write-Host "Downloading artifact ${{ matrix.name}}" 236 | $headers = @{ 237 | "Authorization" = "token ${{ github.token }}" 238 | "Accept" = "application/vnd.github.v3+json" 239 | } 240 | Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri '${{ matrix.url }}' -OutFile '${{ matrix.name }}.zip' 241 | 242 | - name: Upload release artifacts 243 | uses: actions/github-script@v6 244 | env: 245 | releaseId: ${{ needs.createrelease.outputs.releaseId }} 246 | with: 247 | github-token: ${{ secrets.GITHUB_TOKEN }} 248 | script: | 249 | const releaseId = process.env.releaseId 250 | const assetPath = '${{ matrix.name }}.zip' 251 | const assetName = '${{ matrix.name }}.zip' 252 | const fs = require('fs'); 253 | const uploadAssetResponse = await github.rest.repos.uploadReleaseAsset({ 254 | owner: context.repo.owner, 255 | repo: context.repo.repo, 256 | release_id: releaseId, 257 | name: assetName, 258 | data: fs.readFileSync(assetPath) 259 | }); 260 | 261 | - name: nuGetContext 262 | id: nuGetContext 263 | if: ${{ env.nuGetContext }} 264 | run: | 265 | $ErrorActionPreference = "STOP" 266 | Set-StrictMode -version 2.0 267 | $nuGetContext = '' 268 | if ('${{ matrix.atype }}' -eq 'Apps') { 269 | $nuGetContext = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String([System.Environment]::GetEnvironmentVariable('nuGetContext'))) 270 | } 271 | Add-Content -Path $env:GITHUB_OUTPUT -Value "nuGetContext=$nuGetContext" 272 | 273 | - name: Deliver to NuGet 274 | uses: microsoft/AL-Go-Actions/Deliver@v3.1 275 | if: ${{ steps.nuGetContext.outputs.nuGetContext }} 276 | env: 277 | deliveryContext: ${{ steps.nuGetContext.outputs.nuGetContext }} 278 | with: 279 | shell: powershell 280 | type: 'Release' 281 | projects: ${{ matrix.project }} 282 | deliveryTarget: 'NuGet' 283 | artifacts: ${{ github.event.inputs.appVersion }} 284 | atypes: 'Apps,TestApps' 285 | 286 | - name: storageContext 287 | id: storageContext 288 | if: ${{ env.storageContext }} 289 | run: | 290 | $ErrorActionPreference = "STOP" 291 | Set-StrictMode -version 2.0 292 | $storageContext = '' 293 | if ('${{ matrix.atype }}' -eq 'Apps') { 294 | $storageContext = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String([System.Environment]::GetEnvironmentVariable('storageContext'))) 295 | } 296 | Add-Content -Path $env:GITHUB_OUTPUT -Value "storageContext=$storageContext" 297 | 298 | - name: Deliver to Storage 299 | uses: microsoft/AL-Go-Actions/Deliver@v3.1 300 | if: ${{ steps.storageContext.outputs.storageContext }} 301 | env: 302 | deliveryContext: ${{ steps.storageContext.outputs.storageContext }} 303 | with: 304 | shell: powershell 305 | type: 'Release' 306 | projects: ${{ matrix.project }} 307 | deliveryTarget: 'Storage' 308 | artifacts: ${{ github.event.inputs.appVersion }} 309 | atypes: 'Apps,TestApps,Dependencies' 310 | 311 | CreateReleaseBranch: 312 | if: ${{ github.event.inputs.createReleaseBranch=='Y' }} 313 | runs-on: [ windows-latest ] 314 | needs: [ CreateRelease, UploadArtifacts ] 315 | steps: 316 | - name: Checkout 317 | uses: actions/checkout@v3 318 | with: 319 | ref: '${{ needs.createRelease.outputs.commitish }}' 320 | 321 | - name: Create Release Branch 322 | run: | 323 | $ErrorActionPreference = "STOP" 324 | Set-StrictMode -version 2.0 325 | git checkout -b ${{ needs.CreateRelease.outputs.releaseBranch }} 326 | git config user.name ${{ github.actor}} 327 | git config user.email ${{ github.actor}}@users.noreply.github.com 328 | git commit --allow-empty -m "Release branch ${{ needs.CreateRelease.outputs.releaseBranch }}" 329 | git push origin ${{ needs.CreateRelease.outputs.releaseBranch }} 330 | 331 | UpdateVersionNumber: 332 | if: ${{ github.event.inputs.updateVersionNumber!='' }} 333 | runs-on: [ windows-latest ] 334 | needs: [ CreateRelease, UploadArtifacts ] 335 | steps: 336 | - name: Update Version Number 337 | uses: microsoft/AL-Go-Actions/IncrementVersionNumber@v3.1 338 | with: 339 | shell: powershell 340 | parentTelemetryScopeJson: ${{ needs.CreateRelease.outputs.telemetryScopeJson }} 341 | versionNumber: ${{ github.event.inputs.updateVersionNumber }} 342 | directCommit: ${{ github.event.inputs.directCommit }} 343 | 344 | PostProcess: 345 | if: always() 346 | runs-on: [ windows-latest ] 347 | needs: [ CreateRelease, UploadArtifacts, CreateReleaseBranch, UpdateVersionNumber ] 348 | steps: 349 | - name: Checkout 350 | uses: actions/checkout@v3 351 | 352 | - name: Finalize the workflow 353 | id: PostProcess 354 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 355 | with: 356 | shell: powershell 357 | eventId: "DO0094" 358 | telemetryScopeJson: ${{ needs.CreateRelease.outputs.telemetryScopeJson }} 359 | -------------------------------------------------------------------------------- /.github/workflows/CreateTestApp.yaml: -------------------------------------------------------------------------------- 1 | name: 'Create a new test app' 2 | 3 | run-name: "Create a new test app in [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | project: 9 | description: Project name if the repository is setup for multiple projects 10 | required: false 11 | default: '.' 12 | name: 13 | description: Name 14 | required: true 15 | default: '.Test' 16 | publisher: 17 | description: Publisher 18 | required: true 19 | idrange: 20 | description: ID range 21 | required: true 22 | default: '50000..99999' 23 | sampleCode: 24 | description: Include Sample code (Y/N) 25 | required: false 26 | default: 'Y' 27 | directCommit: 28 | description: Direct COMMIT (Y/N) 29 | required: false 30 | default: 'N' 31 | 32 | permissions: 33 | contents: write 34 | pull-requests: write 35 | 36 | defaults: 37 | run: 38 | shell: powershell 39 | 40 | env: 41 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 42 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 43 | 44 | jobs: 45 | CreateTestApp: 46 | runs-on: [ windows-latest ] 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@v3 50 | 51 | - name: Initialize the workflow 52 | id: init 53 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 54 | with: 55 | shell: powershell 56 | eventId: "DO0095" 57 | 58 | - name: Creating a new test app 59 | uses: microsoft/AL-Go-Actions/CreateApp@v3.1 60 | with: 61 | shell: powershell 62 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 63 | project: ${{ github.event.inputs.project }} 64 | type: 'Test App' 65 | name: ${{ github.event.inputs.name }} 66 | publisher: ${{ github.event.inputs.publisher }} 67 | idrange: ${{ github.event.inputs.idrange }} 68 | sampleCode: ${{ github.event.inputs.sampleCode }} 69 | directCommit: ${{ github.event.inputs.directCommit }} 70 | 71 | - name: Finalize the workflow 72 | if: always() 73 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 74 | with: 75 | shell: powershell 76 | eventId: "DO0095" 77 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 78 | -------------------------------------------------------------------------------- /.github/workflows/Current.yaml: -------------------------------------------------------------------------------- 1 | name: ' Test Current' 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: read 8 | 9 | defaults: 10 | run: 11 | shell: powershell 12 | 13 | env: 14 | workflowDepth: 1 15 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 16 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 17 | 18 | jobs: 19 | Initialization: 20 | runs-on: [ windows-latest ] 21 | outputs: 22 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 23 | settings: ${{ steps.ReadSettings.outputs.SettingsJson }} 24 | githubRunner: ${{ steps.ReadSettings.outputs.GitHubRunnerJson }} 25 | githubRunnerShell: ${{ steps.ReadSettings.outputs.GitHubRunnerShell }} 26 | projects: ${{ steps.determineProjectsToBuild.outputs.ProjectsJson }} 27 | projectDependenciesJson: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} 28 | buildOrderJson: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | with: 33 | lfs: true 34 | 35 | - name: Initialize the workflow 36 | id: init 37 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 38 | with: 39 | shell: powershell 40 | eventId: "DO0101" 41 | 42 | - name: Read settings 43 | id: ReadSettings 44 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 45 | with: 46 | shell: powershell 47 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 48 | 49 | - name: Determine Projects To Build 50 | id: determineProjectsToBuild 51 | uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v3.1 52 | with: 53 | shell: powershell 54 | maxBuildDepth: ${{ env.workflowDepth }} 55 | 56 | Build: 57 | needs: [ Initialization ] 58 | if: (!failure()) && (!cancelled()) && fromJson(needs.Initialization.outputs.buildOrderJson)[0].projectsCount > 0 59 | runs-on: ${{ fromJson(needs.Initialization.outputs.githubRunner) }} 60 | defaults: 61 | run: 62 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 63 | strategy: 64 | matrix: 65 | include: ${{ fromJson(needs.Initialization.outputs.buildOrderJson)[0].buildDimensions }} 66 | fail-fast: false 67 | name: Build ${{ matrix.project }} - ${{ matrix.buildMode }} 68 | outputs: 69 | TestResultsArtifactsName: ${{ steps.calculateArtifactNames.outputs.TestResultsArtifactsName }} 70 | BcptTestResultsArtifactsName: ${{ steps.calculateArtifactNames.outputs.BcptTestResultsArtifactsName }} 71 | BuildOutputArtifactsName: ${{ steps.calculateArtifactNames.outputs.BuildOutputArtifactsName }} 72 | steps: 73 | - name: Checkout 74 | uses: actions/checkout@v3 75 | with: 76 | lfs: true 77 | 78 | - name: Download thisbuild artifacts 79 | if: env.workflowDepth > 1 80 | uses: actions/download-artifact@v3 81 | with: 82 | path: '${{ github.workspace }}\.dependencies' 83 | 84 | - name: Read settings 85 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 86 | with: 87 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 88 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 89 | project: ${{ matrix.project }} 90 | 91 | - name: Read secrets 92 | uses: microsoft/AL-Go-Actions/ReadSecrets@v3.1 93 | env: 94 | secrets: ${{ toJson(secrets) }} 95 | with: 96 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 97 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 98 | settingsJson: ${{ env.Settings }} 99 | secrets: 'licenseFileUrl,insiderSasToken,codeSignCertificateUrl,codeSignCertificatePassword,keyVaultCertificateUrl,keyVaultCertificatePassword,keyVaultClientId,gitHubPackagesContext' 100 | 101 | - name: Determine ArtifactUrl 102 | uses: microsoft/AL-Go-Actions/DetermineArtifactUrl@v3.1 103 | id: determineArtifactUrl 104 | with: 105 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 106 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 107 | project: ${{ matrix.project }} 108 | settingsJson: ${{ env.Settings }} 109 | secretsJson: ${{ env.RepoSecrets }} 110 | 111 | - name: Run pipeline 112 | uses: microsoft/AL-Go-Actions/RunPipeline@v3.1 113 | env: 114 | BuildMode: ${{ matrix.buildMode }} 115 | with: 116 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 117 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 118 | project: ${{ matrix.project }} 119 | projectDependenciesJson: ${{ needs.Initialization.outputs.projectDependenciesJson }} 120 | settingsJson: ${{ env.Settings }} 121 | secretsJson: ${{ env.RepoSecrets }} 122 | buildMode: ${{ matrix.buildMode }} 123 | 124 | - name: Calculate Artifact names 125 | id: calculateArtifactsNames 126 | uses: microsoft/AL-Go-Actions/CalculateArtifactNames@v3.1 127 | if: success() || failure() 128 | with: 129 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 130 | settingsJson: ${{ env.Settings }} 131 | project: ${{ matrix.project }} 132 | buildMode: ${{ matrix.buildMode }} 133 | branchName: ${{ github.ref_name }} 134 | suffix: 'Current' 135 | 136 | - name: Upload thisbuild artifacts - apps 137 | if: env.workflowDepth > 1 138 | uses: actions/upload-artifact@v3 139 | with: 140 | name: ${{ steps.calculateArtifactsNames.outputs.ThisBuildAppsArtifactsName }} 141 | path: '${{ matrix.project }}/.buildartifacts/Apps/' 142 | if-no-files-found: ignore 143 | retention-days: 1 144 | 145 | - name: Upload thisbuild artifacts - test apps 146 | if: env.workflowDepth > 1 147 | uses: actions/upload-artifact@v3 148 | with: 149 | name: ${{ steps.calculateArtifactsNames.outputs.ThisBuildTestAppsArtifactsName }} 150 | path: '${{ matrix.project }}/.buildartifacts/TestApps/' 151 | if-no-files-found: ignore 152 | retention-days: 1 153 | 154 | - name: Publish artifacts - build output 155 | uses: actions/upload-artifact@v3 156 | if: (success() || failure()) && (hashFiles(format('{0}/BuildOutput.txt',matrix.project)) != '') 157 | with: 158 | name: ${{ env.buildOutputArtifactsName }} 159 | path: '${{ matrix.project }}/BuildOutput.txt' 160 | if-no-files-found: ignore 161 | 162 | - name: Publish artifacts - container event log 163 | uses: actions/upload-artifact@v3 164 | if: (failure()) && (hashFiles(format('{0}/ContainerEventLog.evtx',matrix.project)) != '') 165 | with: 166 | name: ${{ env.ContainerEventLogArtifactsName }} 167 | path: '${{ matrix.project }}/ContainerEventLog.evtx' 168 | if-no-files-found: ignore 169 | 170 | - name: Publish artifacts - test results 171 | uses: actions/upload-artifact@v3 172 | if: (success() || failure()) && (hashFiles(format('{0}/TestResults.xml',matrix.project)) != '') 173 | with: 174 | name: ${{ env.TestResultsArtifactsName }} 175 | path: '${{ matrix.project }}/TestResults.xml' 176 | if-no-files-found: ignore 177 | 178 | - name: Publish artifacts - bcpt test results 179 | uses: actions/upload-artifact@v3 180 | if: (success() || failure()) && (hashFiles(format('{0}/bcptTestResults.json',matrix.project)) != '') 181 | with: 182 | name: ${{ env.BcptTestResultsArtifactsName }} 183 | path: '${{ matrix.project }}/bcptTestResults.json' 184 | if-no-files-found: ignore 185 | 186 | - name: Analyze Test Results 187 | id: analyzeTestResults 188 | if: success() || failure() 189 | uses: microsoft/AL-Go-Actions/AnalyzeTests@v3.1 190 | with: 191 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 192 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 193 | Project: ${{ matrix.project }} 194 | 195 | - name: Cleanup 196 | if: always() 197 | uses: microsoft/AL-Go-Actions/PipelineCleanup@v3.1 198 | with: 199 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 200 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 201 | Project: ${{ matrix.project }} 202 | 203 | PostProcess: 204 | if: always() 205 | runs-on: [ windows-latest ] 206 | needs: [ Initialization, Build ] 207 | steps: 208 | - name: Checkout 209 | uses: actions/checkout@v3 210 | 211 | - name: Finalize the workflow 212 | id: PostProcess 213 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 214 | with: 215 | shell: powershell 216 | eventId: "DO0101" 217 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 218 | -------------------------------------------------------------------------------- /.github/workflows/IncrementVersionNumber.yaml: -------------------------------------------------------------------------------- 1 | name: ' Increment Version Number' 2 | 3 | run-name: "Increment Version Number in [${{ github.ref_name }}]" 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | project: 9 | description: Project name if the repository is setup for multiple projects (* for all projects) 10 | required: false 11 | default: '*' 12 | versionNumber: 13 | description: Updated Version Number. Use Major.Minor for absolute change, use +Major.Minor for incremental change. 14 | required: true 15 | directCommit: 16 | description: Direct COMMIT (Y/N) 17 | required: false 18 | default: 'N' 19 | 20 | permissions: 21 | contents: write 22 | pull-requests: write 23 | 24 | defaults: 25 | run: 26 | shell: powershell 27 | 28 | env: 29 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 30 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 31 | 32 | jobs: 33 | IncrementVersionNumber: 34 | runs-on: [ windows-latest ] 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v3 38 | 39 | - name: Initialize the workflow 40 | id: init 41 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 42 | with: 43 | shell: powershell 44 | eventId: "DO0096" 45 | 46 | - name: Increment Version Number 47 | uses: microsoft/AL-Go-Actions/IncrementVersionNumber@v3.1 48 | with: 49 | shell: powershell 50 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 51 | project: ${{ github.event.inputs.project }} 52 | versionNumber: ${{ github.event.inputs.versionNumber }} 53 | directCommit: ${{ github.event.inputs.directCommit }} 54 | 55 | - name: Finalize the workflow 56 | if: always() 57 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 58 | with: 59 | shell: powershell 60 | eventId: "DO0096" 61 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 62 | -------------------------------------------------------------------------------- /.github/workflows/NextMajor.yaml: -------------------------------------------------------------------------------- 1 | name: ' Test Next Major' 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: read 8 | 9 | defaults: 10 | run: 11 | shell: powershell 12 | 13 | env: 14 | workflowDepth: 1 15 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 16 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 17 | 18 | jobs: 19 | Initialization: 20 | runs-on: [ windows-latest ] 21 | outputs: 22 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 23 | settings: ${{ steps.ReadSettings.outputs.SettingsJson }} 24 | githubRunner: ${{ steps.ReadSettings.outputs.GitHubRunnerJson }} 25 | githubRunnerShell: ${{ steps.ReadSettings.outputs.GitHubRunnerShell }} 26 | projects: ${{ steps.determineProjectsToBuild.outputs.ProjectsJson }} 27 | projectDependenciesJson: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} 28 | buildOrderJson: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | with: 33 | lfs: true 34 | 35 | - name: Initialize the workflow 36 | id: init 37 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 38 | with: 39 | shell: powershell 40 | eventId: "DO0099" 41 | 42 | - name: Read settings 43 | id: ReadSettings 44 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 45 | with: 46 | shell: powershell 47 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 48 | 49 | - name: Determine Projects To Build 50 | id: determineProjectsToBuild 51 | uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v3.1 52 | with: 53 | shell: powershell 54 | maxBuildDepth: ${{ env.workflowDepth }} 55 | 56 | Build: 57 | needs: [ Initialization ] 58 | if: (!failure()) && (!cancelled()) && fromJson(needs.Initialization.outputs.buildOrderJson)[0].projectsCount > 0 59 | runs-on: ${{ fromJson(needs.Initialization.outputs.githubRunner) }} 60 | defaults: 61 | run: 62 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 63 | strategy: 64 | matrix: 65 | include: ${{ fromJson(needs.Initialization.outputs.buildOrderJson)[0].buildDimensions }} 66 | fail-fast: false 67 | name: Build ${{ matrix.project }} - ${{ matrix.buildMode }} 68 | outputs: 69 | TestResultsArtifactsName: ${{ steps.calculateArtifactNames.outputs.TestResultsArtifactsName }} 70 | BcptTestResultsArtifactsName: ${{ steps.calculateArtifactNames.outputs.BcptTestResultsArtifactsName }} 71 | BuildOutputArtifactsName: ${{ steps.calculateArtifactNames.outputs.BuildOutputArtifactsName }} 72 | steps: 73 | - name: Checkout 74 | uses: actions/checkout@v3 75 | with: 76 | lfs: true 77 | 78 | - name: Download thisbuild artifacts 79 | if: env.workflowDepth > 1 80 | uses: actions/download-artifact@v3 81 | with: 82 | path: '${{ github.workspace }}\.dependencies' 83 | 84 | - name: Read settings 85 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 86 | with: 87 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 88 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 89 | project: ${{ matrix.project }} 90 | 91 | - name: Read secrets 92 | uses: microsoft/AL-Go-Actions/ReadSecrets@v3.1 93 | env: 94 | secrets: ${{ toJson(secrets) }} 95 | with: 96 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 97 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 98 | settingsJson: ${{ env.Settings }} 99 | secrets: 'licenseFileUrl,insiderSasToken,codeSignCertificateUrl,codeSignCertificatePassword,keyVaultCertificateUrl,keyVaultCertificatePassword,keyVaultClientId,gitHubPackagesContext' 100 | 101 | - name: Determine ArtifactUrl 102 | uses: microsoft/AL-Go-Actions/DetermineArtifactUrl@v3.1 103 | id: determineArtifactUrl 104 | with: 105 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 106 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 107 | project: ${{ matrix.project }} 108 | settingsJson: ${{ env.Settings }} 109 | secretsJson: ${{ env.RepoSecrets }} 110 | 111 | - name: Run pipeline 112 | uses: microsoft/AL-Go-Actions/RunPipeline@v3.1 113 | env: 114 | BuildMode: ${{ matrix.buildMode }} 115 | with: 116 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 117 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 118 | project: ${{ matrix.project }} 119 | projectDependenciesJson: ${{ needs.Initialization.outputs.projectDependenciesJson }} 120 | settingsJson: ${{ env.Settings }} 121 | secretsJson: ${{ env.RepoSecrets }} 122 | buildMode: ${{ matrix.buildMode }} 123 | 124 | - name: Calculate Artifact names 125 | id: calculateArtifactsNames 126 | uses: microsoft/AL-Go-Actions/CalculateArtifactNames@v3.1 127 | if: success() || failure() 128 | with: 129 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 130 | settingsJson: ${{ env.Settings }} 131 | project: ${{ matrix.project }} 132 | buildMode: ${{ matrix.buildMode }} 133 | branchName: ${{ github.ref_name }} 134 | suffix: 'NextMajor' 135 | 136 | - name: Upload thisbuild artifacts - apps 137 | if: env.workflowDepth > 1 138 | uses: actions/upload-artifact@v3 139 | with: 140 | name: ${{ steps.calculateArtifactsNames.outputs.ThisBuildAppsArtifactsName }} 141 | path: '${{ matrix.project }}/.buildartifacts/Apps/' 142 | if-no-files-found: ignore 143 | retention-days: 1 144 | 145 | - name: Upload thisbuild artifacts - test apps 146 | if: env.workflowDepth > 1 147 | uses: actions/upload-artifact@v3 148 | with: 149 | name: ${{ steps.calculateArtifactsNames.outputs.ThisBuildTestAppsArtifactsName }} 150 | path: '${{ matrix.project }}/.buildartifacts/TestApps/' 151 | if-no-files-found: ignore 152 | retention-days: 1 153 | 154 | - name: Publish artifacts - build output 155 | uses: actions/upload-artifact@v3 156 | if: (success() || failure()) && (hashFiles(format('{0}/BuildOutput.txt',matrix.project)) != '') 157 | with: 158 | name: ${{ env.buildOutputArtifactsName }} 159 | path: '${{ matrix.project }}/BuildOutput.txt' 160 | if-no-files-found: ignore 161 | 162 | - name: Publish artifacts - container event log 163 | uses: actions/upload-artifact@v3 164 | if: (failure()) && (hashFiles(format('{0}/ContainerEventLog.evtx',matrix.project)) != '') 165 | with: 166 | name: ${{ env.ContainerEventLogArtifactsName }} 167 | path: '${{ matrix.project }}/ContainerEventLog.evtx' 168 | if-no-files-found: ignore 169 | 170 | - name: Publish artifacts - test results 171 | uses: actions/upload-artifact@v3 172 | if: (success() || failure()) && (hashFiles(format('{0}/TestResults.xml',matrix.project)) != '') 173 | with: 174 | name: ${{ env.TestResultsArtifactsName }} 175 | path: '${{ matrix.project }}/TestResults.xml' 176 | if-no-files-found: ignore 177 | 178 | - name: Publish artifacts - bcpt test results 179 | uses: actions/upload-artifact@v3 180 | if: (success() || failure()) && (hashFiles(format('{0}/bcptTestResults.json',matrix.project)) != '') 181 | with: 182 | name: ${{ env.BcptTestResultsArtifactsName }} 183 | path: '${{ matrix.project }}/bcptTestResults.json' 184 | if-no-files-found: ignore 185 | 186 | - name: Analyze Test Results 187 | id: analyzeTestResults 188 | if: success() || failure() 189 | uses: microsoft/AL-Go-Actions/AnalyzeTests@v3.1 190 | with: 191 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 192 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 193 | Project: ${{ matrix.project }} 194 | 195 | - name: Cleanup 196 | if: always() 197 | uses: microsoft/AL-Go-Actions/PipelineCleanup@v3.1 198 | with: 199 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 200 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 201 | Project: ${{ matrix.project }} 202 | 203 | PostProcess: 204 | if: always() 205 | runs-on: [ windows-latest ] 206 | needs: [ Initialization, Build ] 207 | steps: 208 | - name: Checkout 209 | uses: actions/checkout@v3 210 | 211 | - name: Finalize the workflow 212 | id: PostProcess 213 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 214 | with: 215 | shell: powershell 216 | eventId: "DO0099" 217 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 218 | -------------------------------------------------------------------------------- /.github/workflows/NextMinor.yaml: -------------------------------------------------------------------------------- 1 | name: ' Test Next Minor' 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: read 8 | 9 | defaults: 10 | run: 11 | shell: powershell 12 | 13 | env: 14 | workflowDepth: 1 15 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 16 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 17 | 18 | jobs: 19 | Initialization: 20 | runs-on: [ windows-latest ] 21 | outputs: 22 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 23 | settings: ${{ steps.ReadSettings.outputs.SettingsJson }} 24 | githubRunner: ${{ steps.ReadSettings.outputs.GitHubRunnerJson }} 25 | githubRunnerShell: ${{ steps.ReadSettings.outputs.GitHubRunnerShell }} 26 | projects: ${{ steps.determineProjectsToBuild.outputs.ProjectsJson }} 27 | projectDependenciesJson: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} 28 | buildOrderJson: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | with: 33 | lfs: true 34 | 35 | - name: Initialize the workflow 36 | id: init 37 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 38 | with: 39 | shell: powershell 40 | eventId: "DO0100" 41 | 42 | - name: Read settings 43 | id: ReadSettings 44 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 45 | with: 46 | shell: powershell 47 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 48 | 49 | - name: Determine Projects To Build 50 | id: determineProjectsToBuild 51 | uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v3.1 52 | with: 53 | shell: powershell 54 | maxBuildDepth: ${{ env.workflowDepth }} 55 | 56 | Build: 57 | needs: [ Initialization ] 58 | if: (!failure()) && (!cancelled()) && fromJson(needs.Initialization.outputs.buildOrderJson)[0].projectsCount > 0 59 | runs-on: ${{ fromJson(needs.Initialization.outputs.githubRunner) }} 60 | defaults: 61 | run: 62 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 63 | strategy: 64 | matrix: 65 | include: ${{ fromJson(needs.Initialization.outputs.buildOrderJson)[0].buildDimensions }} 66 | fail-fast: false 67 | name: Build ${{ matrix.project }} - ${{ matrix.buildMode }} 68 | outputs: 69 | TestResultsArtifactsName: ${{ steps.calculateArtifactNames.outputs.TestResultsArtifactsName }} 70 | BcptTestResultsArtifactsName: ${{ steps.calculateArtifactNames.outputs.BcptTestResultsArtifactsName }} 71 | BuildOutputArtifactsName: ${{ steps.calculateArtifactNames.outputs.BuildOutputArtifactsName }} 72 | steps: 73 | - name: Checkout 74 | uses: actions/checkout@v3 75 | with: 76 | lfs: true 77 | 78 | - name: Download thisbuild artifacts 79 | if: env.workflowDepth > 1 80 | uses: actions/download-artifact@v3 81 | with: 82 | path: '${{ github.workspace }}\.dependencies' 83 | 84 | - name: Read settings 85 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 86 | with: 87 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 88 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 89 | project: ${{ matrix.project }} 90 | 91 | - name: Read secrets 92 | uses: microsoft/AL-Go-Actions/ReadSecrets@v3.1 93 | env: 94 | secrets: ${{ toJson(secrets) }} 95 | with: 96 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 97 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 98 | settingsJson: ${{ env.Settings }} 99 | secrets: 'licenseFileUrl,insiderSasToken,codeSignCertificateUrl,codeSignCertificatePassword,keyVaultCertificateUrl,keyVaultCertificatePassword,keyVaultClientId,gitHubPackagesContext' 100 | 101 | - name: Determine ArtifactUrl 102 | uses: microsoft/AL-Go-Actions/DetermineArtifactUrl@v3.1 103 | id: determineArtifactUrl 104 | with: 105 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 106 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 107 | project: ${{ matrix.project }} 108 | settingsJson: ${{ env.Settings }} 109 | secretsJson: ${{ env.RepoSecrets }} 110 | 111 | - name: Run pipeline 112 | uses: microsoft/AL-Go-Actions/RunPipeline@v3.1 113 | env: 114 | BuildMode: ${{ matrix.buildMode }} 115 | with: 116 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 117 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 118 | project: ${{ matrix.project }} 119 | projectDependenciesJson: ${{ needs.Initialization.outputs.projectDependenciesJson }} 120 | settingsJson: ${{ env.Settings }} 121 | secretsJson: ${{ env.RepoSecrets }} 122 | buildMode: ${{ matrix.buildMode }} 123 | 124 | - name: Calculate Artifact names 125 | id: calculateArtifactsNames 126 | uses: microsoft/AL-Go-Actions/CalculateArtifactNames@v3.1 127 | if: success() || failure() 128 | with: 129 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 130 | settingsJson: ${{ env.Settings }} 131 | project: ${{ matrix.project }} 132 | buildMode: ${{ matrix.buildMode }} 133 | branchName: ${{ github.ref_name }} 134 | suffix: 'NextMinor' 135 | 136 | - name: Upload thisbuild artifacts - apps 137 | if: env.workflowDepth > 1 138 | uses: actions/upload-artifact@v3 139 | with: 140 | name: ${{ steps.calculateArtifactsNames.outputs.ThisBuildAppsArtifactsName }} 141 | path: '${{ matrix.project }}/.buildartifacts/Apps/' 142 | if-no-files-found: ignore 143 | retention-days: 1 144 | 145 | - name: Upload thisbuild artifacts - test apps 146 | if: env.workflowDepth > 1 147 | uses: actions/upload-artifact@v3 148 | with: 149 | name: ${{ steps.calculateArtifactsNames.outputs.ThisBuildTestAppsArtifactsName }} 150 | path: '${{ matrix.project }}/.buildartifacts/TestApps/' 151 | if-no-files-found: ignore 152 | retention-days: 1 153 | 154 | - name: Publish artifacts - build output 155 | uses: actions/upload-artifact@v3 156 | if: (success() || failure()) && (hashFiles(format('{0}/BuildOutput.txt',matrix.project)) != '') 157 | with: 158 | name: ${{ env.buildOutputArtifactsName }} 159 | path: '${{ matrix.project }}/BuildOutput.txt' 160 | if-no-files-found: ignore 161 | 162 | - name: Publish artifacts - container event log 163 | uses: actions/upload-artifact@v3 164 | if: (failure()) && (hashFiles(format('{0}/ContainerEventLog.evtx',matrix.project)) != '') 165 | with: 166 | name: ${{ env.ContainerEventLogArtifactsName }} 167 | path: '${{ matrix.project }}/ContainerEventLog.evtx' 168 | if-no-files-found: ignore 169 | 170 | - name: Publish artifacts - test results 171 | uses: actions/upload-artifact@v3 172 | if: (success() || failure()) && (hashFiles(format('{0}/TestResults.xml',matrix.project)) != '') 173 | with: 174 | name: ${{ env.TestResultsArtifactsName }} 175 | path: '${{ matrix.project }}/TestResults.xml' 176 | if-no-files-found: ignore 177 | 178 | - name: Publish artifacts - bcpt test results 179 | uses: actions/upload-artifact@v3 180 | if: (success() || failure()) && (hashFiles(format('{0}/bcptTestResults.json',matrix.project)) != '') 181 | with: 182 | name: ${{ env.BcptTestResultsArtifactsName }} 183 | path: '${{ matrix.project }}/bcptTestResults.json' 184 | if-no-files-found: ignore 185 | 186 | - name: Analyze Test Results 187 | id: analyzeTestResults 188 | if: success() || failure() 189 | uses: microsoft/AL-Go-Actions/AnalyzeTests@v3.1 190 | with: 191 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 192 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 193 | Project: ${{ matrix.project }} 194 | 195 | - name: Cleanup 196 | if: always() 197 | uses: microsoft/AL-Go-Actions/PipelineCleanup@v3.1 198 | with: 199 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 200 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 201 | Project: ${{ matrix.project }} 202 | 203 | PostProcess: 204 | if: always() 205 | runs-on: [ windows-latest ] 206 | needs: [ Initialization, Build ] 207 | steps: 208 | - name: Checkout 209 | uses: actions/checkout@v3 210 | 211 | - name: Finalize the workflow 212 | id: PostProcess 213 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 214 | with: 215 | shell: powershell 216 | eventId: "DO0100" 217 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 218 | -------------------------------------------------------------------------------- /.github/workflows/PublishToEnvironment.yaml: -------------------------------------------------------------------------------- 1 | name: ' Publish To Environment' 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | appVersion: 7 | description: App version to deploy to environment(s) (current, prerelease, draft, latest or version number) 8 | required: false 9 | default: 'current' 10 | environmentName: 11 | description: Environment mask to receive the new version (* for all, PROD* for all environments starting with PROD) 12 | required: true 13 | 14 | permissions: 15 | contents: read 16 | actions: read 17 | 18 | defaults: 19 | run: 20 | shell: powershell 21 | 22 | env: 23 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 24 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 25 | 26 | jobs: 27 | Initialization: 28 | runs-on: [ windows-latest ] 29 | outputs: 30 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 31 | settings: ${{ steps.ReadSettings.outputs.SettingsJson }} 32 | environments: ${{ steps.ReadSettings.outputs.EnvironmentsJson }} 33 | environmentCount: ${{ steps.ReadSettings.outputs.EnvironmentCount }} 34 | unknownEnvironment: ${{ steps.ReadSettings.outputs.UnknownEnvironment }} 35 | deviceCode: ${{ steps.Authenticate.outputs.deviceCode }} 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v3 39 | 40 | - name: Initialize the workflow 41 | id: init 42 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 43 | with: 44 | shell: powershell 45 | eventId: "DO0097" 46 | 47 | - name: Read settings 48 | id: ReadSettings 49 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 50 | with: 51 | shell: powershell 52 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 53 | getEnvironments: ${{ github.event.inputs.environmentName }} 54 | includeProduction: 'Y' 55 | 56 | - name: EnvName 57 | id: envName 58 | if: steps.ReadSettings.outputs.UnknownEnvironment == 1 59 | run: | 60 | $ErrorActionPreference = "STOP" 61 | Set-StrictMode -version 2.0 62 | $envName = '${{ fromJson(steps.ReadSettings.outputs.environmentsJson).matrix.include[0].environment }}'.split(' ')[0] 63 | Add-Content -Path $env:GITHUB_OUTPUT -Value "envName=$envName" 64 | 65 | - name: Read secrets 66 | uses: microsoft/AL-Go-Actions/ReadSecrets@v3.1 67 | if: steps.ReadSettings.outputs.UnknownEnvironment == 1 68 | env: 69 | secrets: ${{ toJson(secrets) }} 70 | with: 71 | shell: powershell 72 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 73 | settingsJson: ${{ env.Settings }} 74 | secrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext' 75 | 76 | - name: Authenticate 77 | id: Authenticate 78 | if: steps.ReadSettings.outputs.UnknownEnvironment == 1 79 | run: | 80 | $envName = '${{ steps.envName.outputs.envName }}' 81 | $secretName = '' 82 | $authContext = $null 83 | "$($envName)-AuthContext", "$($envName)_AuthContext", "AuthContext" | ForEach-Object { 84 | if (!($authContext)) { 85 | $authContext = [System.Environment]::GetEnvironmentVariable($_) 86 | if ($authContext) { 87 | Write-Host "Using $_ secret as AuthContext" 88 | $secretName = $_ 89 | } 90 | } 91 | } 92 | if ($authContext) { 93 | Write-Host "AuthContext provided in secret $secretName!" 94 | Set-Content -Path $ENV:GITHUB_STEP_SUMMARY -value "AuthContext was provided in a secret called $secretName. Using this information for authentication." 95 | } 96 | else { 97 | Write-Host "No AuthContext provided for $envName, initiating Device Code flow" 98 | $ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1" 99 | $webClient = New-Object System.Net.WebClient 100 | $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/v3.1/AL-Go-Helper.ps1', $ALGoHelperPath) 101 | . $ALGoHelperPath 102 | $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE 103 | $authContext = New-BcAuthContext -includeDeviceLogin -deviceLoginTimeout ([TimeSpan]::FromSeconds(0)) 104 | CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath 105 | Set-Content -Path $ENV:GITHUB_STEP_SUMMARY -value "AL-Go needs access to the Business Central Environment $('${{ steps.envName.outputs.envName }}'.Split(' ')[0]) and could not locate a secret called ${{ steps.envName.outputs.envName }}_AuthContext`n`n$($authContext.message)" 106 | Add-Content -Path $env:GITHUB_OUTPUT -Value "deviceCode=$($authContext.deviceCode)" 107 | } 108 | 109 | Deploy: 110 | needs: [ Initialization ] 111 | if: needs.Initialization.outputs.environmentCount > 0 112 | strategy: ${{ fromJson(needs.Initialization.outputs.environments) }} 113 | runs-on: ${{ fromJson(matrix.os) }} 114 | name: Deploy to ${{ matrix.environment }} 115 | environment: 116 | name: ${{ matrix.environment }} 117 | env: 118 | deviceCode: ${{ needs.Initialization.outputs.deviceCode }} 119 | steps: 120 | - name: Checkout 121 | uses: actions/checkout@v3 122 | 123 | - name: EnvName 124 | id: envName 125 | run: | 126 | $ErrorActionPreference = "STOP" 127 | Set-StrictMode -version 2.0 128 | $envName = '${{ matrix.environment }}'.split(' ')[0] 129 | Add-Content -Path $env:GITHUB_OUTPUT -Value "envName=$envName" 130 | 131 | - name: Read settings 132 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 133 | with: 134 | shell: powershell 135 | 136 | - name: Read secrets 137 | uses: microsoft/AL-Go-Actions/ReadSecrets@v3.1 138 | env: 139 | secrets: ${{ toJson(secrets) }} 140 | with: 141 | shell: powershell 142 | settingsJson: ${{ env.Settings }} 143 | secrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext,${{ steps.envName.outputs.envName }}-EnvironmentName,${{ steps.envName.outputs.envName }}_EnvironmentName,EnvironmentName,projects' 144 | 145 | - name: AuthContext 146 | id: authContext 147 | run: | 148 | $ErrorActionPreference = "STOP" 149 | Set-StrictMode -version 2.0 150 | $envName = '${{ steps.envName.outputs.envName }}' 151 | $deployToSettingStr = [System.Environment]::GetEnvironmentVariable("DeployTo$envName") 152 | if ($deployToSettingStr) { 153 | $deployToSetting = $deployToSettingStr | ConvertFrom-Json 154 | } 155 | else { 156 | $deployToSetting = [PSCustomObject]@{} 157 | } 158 | $authContext = $null 159 | if ($env:deviceCode) { 160 | $authContext = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("{""deviceCode"":""$($env:deviceCode)""}")) 161 | } 162 | "$($envName)-AuthContext", "$($envName)_AuthContext", "AuthContext" | ForEach-Object { 163 | if (!($authContext)) { 164 | $authContext = [System.Environment]::GetEnvironmentVariable($_) 165 | if ($authContext) { 166 | Write-Host "Using $_ secret as AuthContext" 167 | } 168 | } 169 | } 170 | if (!($authContext)) { 171 | Write-Host "::Error::No AuthContext provided" 172 | exit 1 173 | } 174 | if (("$deployToSetting" -ne "") -and $deployToSetting.PSObject.Properties.name -eq "EnvironmentName") { 175 | $environmentName = $deployToSetting.EnvironmentName 176 | } 177 | else { 178 | $environmentName = $null 179 | "$($envName)-EnvironmentName", "$($envName)_EnvironmentName", "EnvironmentName" | ForEach-Object { 180 | if (!($EnvironmentName)) { 181 | $EnvironmentName = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String([System.Environment]::GetEnvironmentVariable($_))) 182 | if ($EnvironmentName) { 183 | Write-Host "Using $_ secret as EnvironmentName" 184 | Write-Host "Please consider using the DeployTo$_ setting instead, where you can specify EnvironmentName, projects and branches" 185 | } 186 | } 187 | } 188 | } 189 | if (!($environmentName)) { 190 | $environmentName = '${{ steps.envName.outputs.envName }}' 191 | } 192 | $environmentName = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($environmentName + '${{ matrix.environment }}'.SubString($envName.Length)).ToUpperInvariant())) 193 | if (("$deployToSetting" -ne "") -and $deployToSetting.PSObject.Properties.name -eq "projects") { 194 | $projects = $deployToSetting.projects 195 | } 196 | else { 197 | $projects = [System.Environment]::GetEnvironmentVariable("$($envName)-projects") 198 | if (-not $projects) { 199 | $projects = [System.Environment]::GetEnvironmentVariable("$($envName)_Projects") 200 | if (-not $projects) { 201 | $projects = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String([System.Environment]::GetEnvironmentVariable('projects'))) 202 | } 203 | } 204 | } 205 | if ($projects -eq '') { 206 | $projects = '*' 207 | } 208 | elseif ($projects -ne '*') { 209 | $buildProjects = '${{ needs.Initialization.outputs.projects }}' | ConvertFrom-Json 210 | $projects = ($projects.Split(',') | Where-Object { $buildProjects -contains $_ }) -join ',' 211 | } 212 | Add-Content -Path $env:GITHUB_OUTPUT -Value "authContext=$authContext" 213 | Add-Content -Path $env:GITHUB_OUTPUT -Value "environmentName=$environmentName" 214 | Write-Host "environmentName=$([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($environmentName)))" 215 | Write-Host "environmentName (as Base64)=$environmentName" 216 | Add-Content -Path $env:GITHUB_OUTPUT -Value "projects=$projects" 217 | Write-Host "projects=$projects" 218 | 219 | - name: Deploy 220 | uses: microsoft/AL-Go-Actions/Deploy@v3.1 221 | env: 222 | AuthContext: ${{ steps.authContext.outputs.authContext }} 223 | with: 224 | shell: powershell 225 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 226 | type: 'Publish' 227 | projects: ${{ steps.authContext.outputs.projects }} 228 | environmentName: ${{ steps.authContext.outputs.environmentName }} 229 | artifacts: ${{ github.event.inputs.appVersion }} 230 | 231 | PostProcess: 232 | if: always() 233 | runs-on: [ windows-latest ] 234 | needs: [ Initialization, Deploy ] 235 | steps: 236 | - name: Checkout 237 | uses: actions/checkout@v3 238 | 239 | - name: Finalize the workflow 240 | id: PostProcess 241 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 242 | with: 243 | shell: powershell 244 | eventId: "DO0097" 245 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 246 | -------------------------------------------------------------------------------- /.github/workflows/PullRequestHandler.yaml: -------------------------------------------------------------------------------- 1 | name: 'Pull Request Build' 2 | 3 | on: 4 | pull_request_target: 5 | paths-ignore: 6 | - '**.md' 7 | branches: [ 'main' ] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.event.pull_request.number }} 11 | cancel-in-progress: true 12 | 13 | defaults: 14 | run: 15 | shell: powershell 16 | 17 | permissions: 18 | contents: read 19 | actions: read 20 | pull-requests: read 21 | 22 | env: 23 | workflowDepth: 1 24 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 25 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 26 | 27 | jobs: 28 | PregateCheck: 29 | if: github.event.pull_request.base.repo.full_name != github.event.pull_request.head.repo.full_name 30 | runs-on: [ windows-latest ] 31 | steps: 32 | - uses: actions/checkout@v3 33 | with: 34 | lfs: true 35 | ref: refs/pull/${{ github.event.number }}/merge 36 | 37 | - uses: microsoft/AL-Go-Actions/VerifyPRChanges@v3.1 38 | with: 39 | baseSHA: ${{ github.event.pull_request.base.sha }} 40 | headSHA: ${{ github.event.pull_request.head.sha }} 41 | prbaseRepository: ${{ github.event.pull_request.base.repo.full_name }} 42 | 43 | Initialization: 44 | needs: [ PregateCheck ] 45 | if: (!failure() && !cancelled()) 46 | runs-on: [ windows-latest ] 47 | outputs: 48 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 49 | settings: ${{ steps.ReadSettings.outputs.SettingsJson }} 50 | githubRunner: ${{ steps.ReadSettings.outputs.GitHubRunnerJson }} 51 | githubRunnerShell: ${{ steps.ReadSettings.outputs.GitHubRunnerShell }} 52 | projects: ${{ steps.determineProjectsToBuild.outputs.ProjectsJson }} 53 | projectDependenciesJson: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} 54 | buildOrderJson: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} 55 | steps: 56 | - name: Checkout 57 | uses: actions/checkout@v3 58 | with: 59 | lfs: true 60 | ref: refs/pull/${{ github.event.number }}/merge 61 | 62 | - name: Initialize the workflow 63 | id: init 64 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 65 | with: 66 | shell: powershell 67 | eventId: "DO0104" 68 | 69 | - name: Read settings 70 | id: ReadSettings 71 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 72 | with: 73 | shell: powershell 74 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 75 | getEnvironments: '*' 76 | 77 | - name: Determine Projects To Build 78 | id: determineProjectsToBuild 79 | uses: microsoft/AL-Go-Actions/DetermineProjectsToBuild@v3.1 80 | with: 81 | shell: powershell 82 | maxBuildDepth: ${{ env.workflowDepth }} 83 | 84 | Build: 85 | needs: [ Initialization ] 86 | if: (!failure()) && (!cancelled()) && fromJson(needs.Initialization.outputs.buildOrderJson)[0].projectsCount > 0 87 | runs-on: ${{ fromJson(needs.Initialization.outputs.githubRunner) }} 88 | defaults: 89 | run: 90 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 91 | strategy: 92 | matrix: 93 | include: ${{ fromJson(needs.Initialization.outputs.buildOrderJson)[0].buildDimensions }} 94 | fail-fast: false 95 | name: Build ${{ matrix.project }} - ${{ matrix.buildMode }} 96 | outputs: 97 | AppsArtifactsName: ${{ steps.calculateArtifactNames.outputs.AppsArtifactsName }} 98 | TestAppsArtifactsName: ${{ steps.calculateArtifactNames.outputs.TestAppsArtifactsName }} 99 | TestResultsArtifactsName: ${{ steps.calculateArtifactNames.outputs.TestResultsArtifactsName }} 100 | BcptTestResultsArtifactsName: ${{ steps.calculateArtifactNames.outputs.BcptTestResultsArtifactsName }} 101 | BuildOutputArtifactsName: ${{ steps.calculateArtifactNames.outputs.BuildOutputArtifactsName }} 102 | steps: 103 | - name: Checkout 104 | uses: actions/checkout@v3 105 | with: 106 | lfs: true 107 | ref: refs/pull/${{ github.event.number }}/merge 108 | 109 | - name: Download thisbuild artifacts 110 | if: env.workflowDepth > 1 111 | uses: actions/download-artifact@v3 112 | with: 113 | path: '.dependencies' 114 | 115 | - name: Read settings 116 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 117 | with: 118 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 119 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 120 | project: ${{ matrix.project }} 121 | 122 | - name: Read secrets 123 | uses: microsoft/AL-Go-Actions/ReadSecrets@v3.1 124 | env: 125 | secrets: ${{ toJson(secrets) }} 126 | with: 127 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 128 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 129 | settingsJson: ${{ env.Settings }} 130 | secrets: 'licenseFileUrl,insiderSasToken,keyVaultCertificateUrl,keyVaultCertificatePassword,keyVaultClientId,gitHubPackagesContext' 131 | 132 | - name: Determine ArtifactUrl 133 | uses: microsoft/AL-Go-Actions/DetermineArtifactUrl@v3.1 134 | id: determineArtifactUrl 135 | with: 136 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 137 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 138 | project: ${{ matrix.project }} 139 | settingsJson: ${{ env.Settings }} 140 | secretsJson: ${{ env.RepoSecrets }} 141 | 142 | - name: Cache Business Central Artifacts 143 | if: env.useCompilerFolder == 'True' && steps.determineArtifactUrl.outputs.ArtifactUrl && !contains(env.artifact,'INSIDERSASTOKEN') 144 | uses: actions/cache@v3 145 | with: 146 | path: .artifactcache 147 | key: ${{ steps.determineArtifactUrl.outputs.ArtifactUrl }} 148 | 149 | - name: Run pipeline 150 | id: RunPipeline 151 | uses: microsoft/AL-Go-Actions/RunPipeline@v3.1 152 | env: 153 | BuildMode: ${{ matrix.buildMode }} 154 | with: 155 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 156 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 157 | project: ${{ matrix.project }} 158 | projectDependenciesJson: ${{ needs.Initialization.outputs.projectDependenciesJson }} 159 | settingsJson: ${{ env.Settings }} 160 | secretsJson: ${{ env.RepoSecrets }} 161 | buildMode: ${{ matrix.buildMode }} 162 | 163 | - name: Calculate Artifact names 164 | id: calculateArtifactsNames 165 | uses: microsoft/AL-Go-Actions/CalculateArtifactNames@v3.1 166 | if: success() || failure() 167 | with: 168 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 169 | settingsJson: ${{ env.Settings }} 170 | project: ${{ matrix.project }} 171 | buildMode: ${{ matrix.buildMode }} 172 | branchName: ${{ github.ref_name }} 173 | 174 | - name: Upload thisbuild artifacts - apps 175 | if: env.workflowDepth > 1 176 | uses: actions/upload-artifact@v3 177 | with: 178 | name: ${{ steps.calculateArtifactsNames.outputs.ThisBuildAppsArtifactsName }} 179 | path: '${{ matrix.project }}/.buildartifacts/Apps/' 180 | if-no-files-found: ignore 181 | retention-days: 1 182 | 183 | - name: Upload thisbuild artifacts - test apps 184 | if: env.workflowDepth > 1 185 | uses: actions/upload-artifact@v3 186 | with: 187 | name: ${{ steps.calculateArtifactsNames.outputs.ThisBuildTestAppsArtifactsName }} 188 | path: '${{ matrix.project }}/.buildartifacts/TestApps/' 189 | if-no-files-found: ignore 190 | retention-days: 1 191 | 192 | - name: Publish artifacts - build output 193 | uses: actions/upload-artifact@v3 194 | if: (success() || failure()) && (hashFiles(format('{0}/BuildOutput.txt',matrix.project)) != '') 195 | with: 196 | name: ${{ env.BuildOutputArtifactsName }} 197 | path: '${{ matrix.project }}/BuildOutput.txt' 198 | if-no-files-found: ignore 199 | 200 | - name: Publish artifacts - container event log 201 | uses: actions/upload-artifact@v3 202 | if: (failure()) && (hashFiles(format('{0}/ContainerEventLog.evtx',matrix.project)) != '') 203 | with: 204 | name: ${{ env.ContainerEventLogArtifactsName }} 205 | path: '${{ matrix.project }}/ContainerEventLog.evtx' 206 | if-no-files-found: ignore 207 | 208 | - name: Publish artifacts - test results 209 | uses: actions/upload-artifact@v3 210 | if: (success() || failure()) && (hashFiles(format('{0}/TestResults.xml',matrix.project)) != '') 211 | with: 212 | name: ${{ env.TestResultsArtifactsName }} 213 | path: '${{ matrix.project }}/TestResults.xml' 214 | if-no-files-found: ignore 215 | 216 | - name: Publish artifacts - bcpt test results 217 | uses: actions/upload-artifact@v3 218 | if: (success() || failure()) && (hashFiles(format('{0}/bcptTestResults.json',matrix.project)) != '') 219 | with: 220 | name: ${{ env.BcptTestResultsArtifactsName }} 221 | path: '${{ matrix.project }}/bcptTestResults.json' 222 | if-no-files-found: ignore 223 | 224 | - name: Analyze Test Results 225 | id: analyzeTestResults 226 | if: success() || failure() 227 | uses: microsoft/AL-Go-Actions/AnalyzeTests@v3.1 228 | with: 229 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 230 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 231 | Project: ${{ matrix.project }} 232 | 233 | - name: Cleanup 234 | if: always() 235 | uses: microsoft/AL-Go-Actions/PipelineCleanup@v3.1 236 | with: 237 | shell: ${{ needs.Initialization.outputs.githubRunnerShell }} 238 | parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 239 | Project: ${{ matrix.project }} 240 | 241 | PostProcess: 242 | runs-on: [ windows-latest ] 243 | needs: [ Initialization, Build ] 244 | if: (!cancelled()) 245 | steps: 246 | - name: Checkout 247 | uses: actions/checkout@v3 248 | with: 249 | lfs: true 250 | ref: refs/pull/${{ github.event.number }}/merge 251 | 252 | - name: Finalize the workflow 253 | id: PostProcess 254 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 255 | with: 256 | shell: powershell 257 | eventId: "DO0104" 258 | telemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} 259 | -------------------------------------------------------------------------------- /.github/workflows/UpdateGitHubGoSystemFiles.yaml: -------------------------------------------------------------------------------- 1 | name: ' Update AL-Go System Files' 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | templateUrl: 7 | description: Template Repository URL (current is https://github.com/microsoft/AL-Go-PTE@main) 8 | required: false 9 | default: '' 10 | directCommit: 11 | description: Direct COMMIT (Y/N) 12 | required: false 13 | default: 'N' 14 | 15 | permissions: 16 | contents: read 17 | 18 | defaults: 19 | run: 20 | shell: powershell 21 | 22 | env: 23 | ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} 24 | ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} 25 | 26 | jobs: 27 | UpdateALGoSystemFiles: 28 | runs-on: [ windows-latest ] 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | 33 | - name: Initialize the workflow 34 | id: init 35 | uses: microsoft/AL-Go-Actions/WorkflowInitialize@v3.1 36 | with: 37 | shell: powershell 38 | eventId: "DO0098" 39 | 40 | - name: Read settings 41 | uses: microsoft/AL-Go-Actions/ReadSettings@v3.1 42 | with: 43 | shell: powershell 44 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 45 | get: keyVaultName,ghTokenWorkflowSecretName,templateUrl 46 | 47 | - name: Read secrets 48 | uses: microsoft/AL-Go-Actions/ReadSecrets@v3.1 49 | env: 50 | secrets: ${{ toJson(secrets) }} 51 | with: 52 | shell: powershell 53 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 54 | settingsJson: ${{ env.Settings }} 55 | secrets: 'ghTokenWorkflow=${{ env.GHTOKENWORKFLOWSECRETNAME }}' 56 | 57 | - name: Override templateUrl 58 | env: 59 | templateUrl: ${{ github.event.inputs.templateUrl }} 60 | run: | 61 | $ErrorActionPreference = "STOP" 62 | Set-StrictMode -version 2.0 63 | $templateUrl = $ENV:templateUrl 64 | if ($templateUrl) { 65 | Write-Host "Using Template Url: $templateUrl" 66 | Add-Content -Path $env:GITHUB_ENV -Value "templateUrl=$templateUrl" 67 | } 68 | 69 | - name: Calculate DirectCommit 70 | env: 71 | directCommit: ${{ github.event.inputs.directCommit }} 72 | eventName: ${{ github.event_name }} 73 | run: | 74 | $ErrorActionPreference = "STOP" 75 | Set-StrictMode -version 2.0 76 | $directCommit = $ENV:directCommit 77 | Write-Host $ENV:eventName 78 | if ($ENV:eventName -eq 'schedule') { 79 | Write-Host "Running Update AL-Go System Files on a schedule. Setting DirectCommit = Y" 80 | $directCommit = 'Y' 81 | } 82 | Add-Content -Path $env:GITHUB_ENV -Value "DirectCommit=$directCommit" 83 | 84 | - name: Update AL-Go system files 85 | uses: microsoft/AL-Go-Actions/CheckForUpdates@v3.1 86 | with: 87 | shell: powershell 88 | parentTelemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 89 | token: ${{ env.ghTokenWorkflow }} 90 | Update: Y 91 | templateUrl: ${{ env.templateUrl }} 92 | directCommit: ${{ env.directCommit }} 93 | 94 | - name: Finalize the workflow 95 | if: always() 96 | uses: microsoft/AL-Go-Actions/WorkflowPostProcess@v3.1 97 | with: 98 | shell: powershell 99 | eventId: "DO0098" 100 | telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.app 2 | *.flf 3 | *.bclicense 4 | *.g.xlf 5 | .DS_Store 6 | Thumbs.db 7 | TestResults*.xml 8 | bcptTestResults*.json 9 | BuildOutput.txt 10 | rad.json 11 | .output/ 12 | .dependencies/ 13 | .buildartifacts/ 14 | .alpackages/ 15 | .packages/ 16 | .alcache/ 17 | .altemplates/ 18 | .snapshots/ 19 | cache_* 20 | ~$* 21 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @microsoft/dynamics-smb-engineering-systems 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AL-Go Template 2 | ## Per Tenant Extension Project 3 | This template repository can be used for managing Per Tenant Extensions for Business Central. 4 | 5 | Please consult https://github.com/microsoft/AL-Go/#readme for scenarios on usage 6 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This GitHub repo is auto-generated from https://github.com/microsoft/AL-Go 6 | Issues, bug tracking and feature requests should be done there. 7 | 8 | Please follow the documentation [here](https://github.com/microsoft/AL-Go/blob/main/Scenarios/Contribute.md) if you want to contribute to AL-Go for GitHub. 9 | 10 | ## Microsoft Support Policy 11 | 12 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 13 | -------------------------------------------------------------------------------- /SystemAppPresentation/SystemAppExamples.permissionset.al: -------------------------------------------------------------------------------- 1 | permissionset 54100 SystemAppExamples 2 | { 3 | Assignable = true; 4 | Permissions = tabledata "MNB Azure Function Setup"=RIMD, 5 | tabledata "MNB Azure Storage Setup"=RIMD, 6 | tabledata "MNB My Log Table"=RIMD, 7 | tabledata "MNB SharePoint Setup"=RIMD, 8 | table "MNB Azure Function Setup"=X, 9 | table "MNB Azure Storage Setup"=X, 10 | table "MNB My Log Table"=X, 11 | table "MNB SharePoint Setup"=X, 12 | report "MNB Item Barcode"=X, 13 | codeunit "MNB Asset Location Mgt"=X, 14 | codeunit "MNB Azure Blob Storage"=X, 15 | codeunit "MNB Azure Function Mgt"=X, 16 | codeunit "MNB Base64"=X, 17 | codeunit "MNB Camera and Image"=X, 18 | codeunit "MNB Checklist Install"=X, 19 | codeunit "MNB Email"=X, 20 | codeunit "MNB Environment Info"=X, 21 | codeunit "MNB Filter Tokens"=X, 22 | codeunit "MNB Object and Fields"=X, 23 | codeunit "MNB Occurrence Mgt."=X, 24 | codeunit "MNB Persistent Blob"=X, 25 | codeunit "MNB Reten. Pol. Install"=X, 26 | codeunit "MNB SharePoint Mgt."=X, 27 | codeunit "MNB XML Export"=X, 28 | codeunit "MNB Zip"=X, 29 | page "MNB Azure Containers"=X, 30 | page "MNB Azure Function Setup"=X, 31 | page "MNB Azure Storage Setup"=X, 32 | page "MNB Base64"=X, 33 | page "MNB Choosing Objects"=X, 34 | page "MNB Container Content"=X, 35 | page "MNB Math Operations"=X, 36 | page "MNB Next Occurrence"=X, 37 | page "MNB Password Operations"=X, 38 | page "MNB Persistent Blob"=X, 39 | page "MNB Purchase Order Picture"=X, 40 | page "MNB RegEx Examples"=X, 41 | page "MNB SharePoint Files"=X, 42 | page "MNB SharePoint Folders"=X, 43 | page "MNB SharePoint Setup"=X; 44 | } -------------------------------------------------------------------------------- /SystemAppPresentation/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cf2b1144-9cff-4f53-b6fe-17581ee30322", 3 | "name": "System App Presentation", 4 | "publisher": "BC4All.com", 5 | "version": "1.0.0.0", 6 | "brief": "", 7 | "description": "This extension shows examples for System App. ", 8 | "privacyStatement": "", 9 | "EULA": "", 10 | "help": "", 11 | "url": "", 12 | "logo": "", 13 | "dependencies": [], 14 | "screenshots": [], 15 | "platform": "1.0.0.0", 16 | "application": "21.0.0.0", 17 | "idRanges": [ 18 | { 19 | "from": 54100, 20 | "to": 54149 21 | } 22 | ], 23 | "resourceExposurePolicy": { 24 | "allowDebugging": true, 25 | "allowDownloadingSource": false, 26 | "includeSourceInSymbolFile": false 27 | }, 28 | "runtime": "11.0" 29 | 30 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/10_SharePoint/SharePointFiles.Page.al: -------------------------------------------------------------------------------- 1 | page 54109 "MNB SharePoint Files" 2 | { 3 | PageType = List; 4 | UsageCategory = None; 5 | SourceTable = "SharePoint File"; 6 | SourceTableTemporary = true; 7 | Editable = false; 8 | ApplicationArea = All; 9 | 10 | layout 11 | { 12 | area(Content) 13 | { 14 | repeater(Folders) 15 | { 16 | field(Name; Rec.Name) 17 | { 18 | ApplicationArea = All; 19 | ToolTip = 'Specifies the value of the Title field.'; 20 | } 21 | field("Server Relative Url"; Rec."Server Relative Url") 22 | { 23 | ToolTip = 'Specifies the value of the Server Relative Url field.'; 24 | } 25 | } 26 | 27 | } 28 | } 29 | 30 | procedure SetData(var SharePointFile: Record "SharePoint File" temporary) 31 | begin 32 | Rec.Copy(SharePointFile, true); 33 | end; 34 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/10_SharePoint/SharePointLists.Page.al: -------------------------------------------------------------------------------- 1 | page 54108 "MNB SharePoint Folders" 2 | { 3 | PageType = List; 4 | UsageCategory = None; 5 | SourceTable = "SharePoint Folder"; 6 | SourceTableTemporary = true; 7 | Editable = false; 8 | ApplicationArea = All; 9 | 10 | layout 11 | { 12 | area(Content) 13 | { 14 | repeater(Folders) 15 | { 16 | field(Name; Rec.Name) 17 | { 18 | ApplicationArea = All; 19 | ToolTip = 'Specifies the value of the Title field.'; 20 | } 21 | field("Item Count"; Rec."Item Count") 22 | { 23 | ToolTip = 'Specifies the value of the Item Count field.'; 24 | } 25 | } 26 | } 27 | 28 | } 29 | procedure SetData(var SharePointFolder: Record "SharePoint Folder" temporary) 30 | begin 31 | Rec.Copy(SharePointFolder, true); 32 | end; 33 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/10_SharePoint/SharePointMgt.Codeunit.al: -------------------------------------------------------------------------------- 1 | codeunit 54108 "MNB SharePoint Mgt." 2 | { 3 | internal procedure ShowFolders(): Text; 4 | var 5 | MNBSharePointSetup: Record "MNB SharePoint Setup"; 6 | SharePointFolder: Record "SharePoint Folder" temporary; 7 | SharePointClient: Codeunit "SharePoint Client"; 8 | MNBSharePointFolders: Page "MNB SharePoint Folders"; 9 | AadTenantId, ServerRelativeUrl : Text; 10 | begin 11 | MNBSharePointSetup.Get(); 12 | AadTenantId := GetAadTenantNameFromBaseUrl(MNBSharePointSetup."Base URL"); 13 | SharePointClient.Initialize(MNBSharePointSetup."Base URL", GetSharePointAuthorization(AadTenantId)); 14 | SharePointClient.GetSubFoldersByServerRelativeUrl(ServerRelativeUrl, SharePointFolder); 15 | 16 | MNBSharePointFolders.SetData(SharePointFolder); 17 | MNBSharePointFolders.LookupMode(true); 18 | if MNBSharePointFolders.RunModal() = Action::LookupOK then begin 19 | MNBSharePointFolders.GetRecord(SharePointFolder); 20 | exit(SharePointFolder.Name); 21 | end; 22 | end; 23 | 24 | internal procedure UploadFile() 25 | var 26 | MNBSharePointSetup: Record "MNB SharePoint Setup"; 27 | SharePointFile: Record "SharePoint File"; 28 | FileManagement: Codeunit "File Management"; 29 | SharePointClient: Codeunit "SharePoint Client"; 30 | TempBlob: Codeunit "Temp Blob"; 31 | InStream: InStream; 32 | HttpDiagnostics: Interface "HTTP Diagnostics"; 33 | SaveFailedErr: Label 'Save to SharePoint failed.\ErrorMessage: %1\HttpRetryAfter: %2\HttpStatusCode: %3\ResponseReasonPhrase: %4', Comment = '%1=GetErrorMessage; %2=GetHttpRetryAfter; %3=GetHttpStatusCode; %4=GetResponseReasonPhrase'; 34 | AadTenantId: Text; 35 | FileName: Text; 36 | begin 37 | MNBSharePointSetup.Get(); 38 | FileName := FileManagement.BLOBImportWithFilter(TempBlob, 'Import File', 'Import File', 'All Files (*.*)|*.*', '*.*'); 39 | AadTenantId := GetAadTenantNameFromBaseUrl(MNBSharePointSetup."Base URL"); 40 | SharePointClient.Initialize(MNBSharePointSetup."Base URL", GetSharePointAuthorization(AadTenantId)); 41 | InStream := TempBlob.CreateInStream(); 42 | if not SharePointClient.AddFileToFolder(MNBSharePointSetup."Folder Name", FileName, InStream, SharePointFile) then begin 43 | HttpDiagnostics := SharePointClient.GetDiagnostics(); 44 | Error(SaveFailedErr, HttpDiagnostics.GetErrorMessage(), HttpDiagnostics.GetHttpRetryAfter(), HttpDiagnostics.GetHttpStatusCode(), HttpDiagnostics.GetResponseReasonPhrase()); 45 | end; 46 | end; 47 | 48 | internal procedure ListFiles() 49 | var 50 | MNBSharePointSetup: Record "MNB SharePoint Setup"; 51 | SharePointFile: Record "SharePoint File"; 52 | SharePointClient: Codeunit "SharePoint Client"; 53 | MNBSharePointFiles: Page "MNB SharePoint Files"; 54 | AadTenantId: Text; 55 | begin 56 | MNBSharePointSetup.Get(); 57 | AadTenantId := GetAadTenantNameFromBaseUrl(MNBSharePointSetup."Base URL"); 58 | SharePointClient.Initialize(MNBSharePointSetup."Base URL", GetSharePointAuthorization(AadTenantId)); 59 | SharePointClient.GetFolderFilesByServerRelativeUrl(MNBSharePointSetup."Folder Name", SharePointFile); 60 | MNBSharePointFiles.SetData(SharePointFile); 61 | MNBSharePointFiles.LookupMode(true); 62 | MNBSharePointFiles.RunModal(); 63 | end; 64 | 65 | local procedure GetSharePointAuthorization(AadTenantId: Text): Interface "SharePoint Authorization" 66 | var 67 | MNBSharePointSetup: Record "MNB SharePoint Setup"; 68 | SharePointAuth: Codeunit "SharePoint Auth."; 69 | Scopes: List of [Text]; 70 | begin 71 | MNBSharePointSetup.Get(); 72 | Scopes.Add('00000003-0000-0ff1-ce00-000000000000/.default'); 73 | exit(SharePointAuth.CreateAuthorizationCode(AadTenantId, MNBSharePointSetup."Client Id", MNBSharePointSetup."Client Secret", Scopes)); 74 | end; 75 | 76 | local procedure GetAadTenantNameFromBaseUrl(BaseUrl: Text): Text 77 | var 78 | Uri: Codeunit Uri; 79 | MySiteHostSuffixTxt: Label '-my.sharepoint.com', Locked = true; 80 | OnMicrosoftTxt: Label '.onmicrosoft.com', Locked = true; 81 | SharePointHostSuffixTxt: Label '.sharepoint.com', Locked = true; 82 | UrlInvalidErr: Label 'The Base Url %1 does not seem to be a valid SharePoint Online Url.', Comment = '%1=BaseUrl'; 83 | Host: Text; 84 | begin 85 | // SharePoint Online format: https://tenantname.sharepoint.com/SiteName/LibraryName/ 86 | // SharePoint My Site format: https://tenantname-my.sharepoint.com/personal/user_name/ 87 | Uri.Init(BaseUrl); 88 | Host := Uri.GetHost(); 89 | if not Host.EndsWith(SharePointHostSuffixTxt) then 90 | Error(UrlInvalidErr, BaseUrl); 91 | if Host.EndsWith(MySiteHostSuffixTxt) then 92 | exit(CopyStr(Host, 1, StrPos(Host, MySiteHostSuffixTxt) - 1) + OnMicrosoftTxt); 93 | exit(CopyStr(Host, 1, StrPos(Host, SharePointHostSuffixTxt) - 1) + OnMicrosoftTxt); 94 | end; 95 | 96 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/10_SharePoint/SharePointSetup.Page.al: -------------------------------------------------------------------------------- 1 | page 54107 "MNB SharePoint Setup" 2 | { 3 | Caption = 'SharePoint Setup'; 4 | PageType = Card; 5 | SourceTable = "MNB SharePoint Setup"; 6 | ApplicationArea = All; 7 | UsageCategory = Administration; 8 | InsertAllowed = false; 9 | DeleteAllowed = false; 10 | 11 | layout 12 | { 13 | area(content) 14 | { 15 | group(General) 16 | { 17 | Caption = 'General'; 18 | field("Client Id"; Rec."Client Id") 19 | { 20 | ApplicationArea = All; 21 | ToolTip = 'Specifies the value of the Client Id field.'; 22 | } 23 | field("Client Secret"; Rec."Client Secret") 24 | { 25 | ToolTip = 'Specifies the value of the Client Secret field.'; 26 | ApplicationArea = All; 27 | 28 | } 29 | field("Base URL"; Rec."Base URL") 30 | { 31 | ToolTip = 'Specifies the value of the Base URL field.'; 32 | ApplicationArea = All; 33 | } 34 | field("Folder Name"; Rec."Folder Name") 35 | { 36 | ToolTip = 'Specifies the value of the Folder Name field.'; 37 | ApplicationArea = All; 38 | Editable = false; 39 | } 40 | } 41 | } 42 | } 43 | actions 44 | { 45 | area(Processing) 46 | { 47 | action(UploadFile) 48 | { 49 | Caption = 'Upload File to SharePoint'; 50 | Image = ShowList; 51 | ToolTip = 'Upload File to SharePoint'; 52 | Promoted = true; 53 | PromotedCategory = Process; 54 | ApplicationArea = All; 55 | trigger OnAction() 56 | var 57 | MNBSharePointMgt: Codeunit "MNB SharePoint Mgt."; 58 | begin 59 | MNBSharePointMgt.UploadFile(); 60 | end; 61 | } 62 | action(ListFolders) 63 | { 64 | Caption = 'List Folders'; 65 | Image = ShowList; 66 | ToolTip = 'Show list of folders'; 67 | Promoted = true; 68 | PromotedCategory = Process; 69 | ApplicationArea = All; 70 | trigger OnAction() 71 | var 72 | MNBSharePointMgt: Codeunit "MNB SharePoint Mgt."; 73 | begin 74 | Rec."Folder Name" := MNBSharePointMgt.ShowFolders(); 75 | end; 76 | } 77 | action(ListFiles) 78 | { 79 | Caption = 'List Files'; 80 | Image = FileContract; 81 | ToolTip = 'Show list of files in the SharePoint.'; 82 | Promoted = true; 83 | PromotedCategory = Process; 84 | ApplicationArea = All; 85 | trigger OnAction() 86 | var 87 | MNBSharePointMgt: Codeunit "MNB SharePoint Mgt."; 88 | begin 89 | MNBSharePointMgt.ListFiles(); 90 | end; 91 | } 92 | } 93 | } 94 | 95 | trigger OnOpenPage() 96 | begin 97 | if not Rec.Get() then begin 98 | Rec.Init(); 99 | Rec.Insert(); 100 | end; 101 | end; 102 | } 103 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/10_SharePoint/SharePointSetup.Table.al: -------------------------------------------------------------------------------- 1 | table 54102 "MNB SharePoint Setup" 2 | { 3 | DataClassification = SystemMetadata; 4 | Caption = 'SharePoint Setup'; 5 | 6 | fields 7 | { 8 | field(1; "Primary Key"; Code[10]) 9 | { 10 | Caption = 'Primary Key'; 11 | DataClassification = SystemMetadata; 12 | } 13 | field(2; "Client Id"; Text[250]) 14 | { 15 | Caption = 'Client Id'; 16 | DataClassification = SystemMetadata; 17 | } 18 | field(3; "Client Secret"; Text[1024]) 19 | { 20 | Caption = 'Client Secret'; 21 | DataClassification = SystemMetadata; 22 | ExtendedDatatype = Masked; 23 | } 24 | field(4; "Base URL"; Text[250]) 25 | { 26 | Caption = 'Base URL'; 27 | DataClassification = SystemMetadata; 28 | } 29 | field(5; "Folder Name"; Text[250]) 30 | { 31 | Caption = 'Folder Name'; 32 | DataClassification = SystemMetadata; 33 | } 34 | } 35 | 36 | keys 37 | { 38 | key(PK; "Primary Key") 39 | { 40 | Clustered = true; 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/11_Password/PasswordOperations.Page.al: -------------------------------------------------------------------------------- 1 | page 54110 "MNB Password Operations" 2 | { 3 | /// 4 | /// Shows example of math module 5 | /// 6 | /// 7 | 8 | Caption = 'Password Operations'; 9 | PageType = Card; 10 | ApplicationArea = All; 11 | UsageCategory = Tasks; 12 | 13 | layout 14 | { 15 | area(content) 16 | { 17 | group(General) 18 | { 19 | Caption = 'General'; 20 | field(PasswordText; Password) 21 | { 22 | Editable = false; 23 | Caption = 'My Password'; 24 | } 25 | } 26 | } 27 | } 28 | actions 29 | { 30 | area(Processing) 31 | { 32 | 33 | action(TypePassword) 34 | { 35 | Caption = 'Type Password'; 36 | ToolTip = 'Type password for the user.'; 37 | Image = Absence; 38 | Promoted = true; 39 | PromotedCategory = Process; 40 | PromotedOnly = true; 41 | ApplicationArea = All; 42 | trigger OnAction() 43 | var 44 | PasswordDialogManagement: Codeunit "Password Dialog Management"; 45 | PasswordToCheck: Text; 46 | MyPasswordTxt: Label 'My Top Secret Password does not match.'; 47 | begin 48 | PasswordToCheck := PasswordDialogManagement.OpenPasswordDialog(); 49 | if PasswordToCheck <> Password then 50 | Error(MyPasswordTxt, Password); 51 | end; 52 | } 53 | action(ChangePassword) 54 | { 55 | Caption = 'Change Password'; 56 | ToolTip = 'Set password for the user.'; 57 | Image = Absence; 58 | Promoted = true; 59 | PromotedCategory = Process; 60 | PromotedOnly = true; 61 | ApplicationArea = All; 62 | trigger OnAction() 63 | var 64 | NewPassword: Text; 65 | PasswordDialogManagement: Codeunit "Password Dialog Management"; 66 | begin 67 | PasswordDialogManagement.OpenChangePasswordDialog(Password, NewPassword); 68 | Password := NewPassword; 69 | end; 70 | } 71 | } 72 | } 73 | var 74 | Password: Text; 75 | } 76 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/12_RegEx/RegExExamples.Page.al: -------------------------------------------------------------------------------- 1 | page 54111 "MNB RegEx Examples" 2 | { 3 | Caption = 'RegEx Examples'; 4 | PageType = Card; 5 | ApplicationArea = All; 6 | UsageCategory = Tasks; 7 | 8 | layout 9 | { 10 | area(Content) 11 | { 12 | group(Examples) 13 | { 14 | field(RegExTxtField; RegExTxt) 15 | { 16 | ApplicationArea = All; 17 | Caption = 'Regular Expression'; 18 | ToolTip = 'Specifies the value of the Regular Expression field.'; 19 | } 20 | field(TextToLookField; TextToLook) 21 | { 22 | Caption = 'Text'; 23 | MultiLine = true; 24 | ToolTip = 'Specifies the value of the Text field.'; 25 | } 26 | } 27 | } 28 | } 29 | 30 | actions 31 | { 32 | area(Processing) 33 | { 34 | action(Find) 35 | { 36 | ApplicationArea = All; 37 | Caption = 'Find in Text'; 38 | ToolTip = 'Executes the ActionName action.'; 39 | trigger OnAction() 40 | var 41 | Regex: Codeunit Regex; 42 | NoMatchErr: Label 'No Match for %1', Comment = '%1 - regular expression'; 43 | MatchMsg: Label 'There is a match for %1', Comment = '%1 - regular expression'; 44 | begin 45 | if not Regex.IsMatch(TextToLook, RegExTxt) then 46 | Error(NoMatchErr, RegExTxt) 47 | else 48 | Message(MatchMsg, RegExTxt); 49 | end; 50 | } 51 | } 52 | } 53 | 54 | var 55 | RegExTxt: Text; 56 | TextToLook: Text; 57 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/13_AzureFunction/AzureFunctionMgt.Codeunit.al: -------------------------------------------------------------------------------- 1 | codeunit 54109 "MNB Azure Function Mgt" 2 | { 3 | internal procedure RunAzureFunction() 4 | var 5 | MNBAzureFunctionSetup: Record "MNB Azure Function Setup"; 6 | AzureFunctionsAuthenticationIn: Interface "Azure Functions Authentication"; 7 | AzureFunctionsAuthentication: Codeunit "Azure Functions Authentication"; 8 | AzureFunctions: Codeunit "Azure Functions"; 9 | AzureFunctionsResponse: Codeunit "Azure Functions Response"; 10 | QueryDict: Dictionary of [Text, Text]; 11 | ResponseTxt: Text; 12 | ResponseMsg: Label 'Response from Azure Function: %1', Comment = '%1 - response from azure function'; 13 | begin 14 | MNBAzureFunctionSetup.Get(); 15 | AzureFunctionsAuthenticationIn := AzureFunctionsAuthentication.CreateCodeAuth(MNBAzureFunctionSetup."Azure Function Url", ''); 16 | 17 | AzureFunctionsResponse := AzureFunctions.SendGetRequest(AzureFunctionsAuthenticationIn, QueryDict); 18 | AzureFunctionsResponse.GetResultAsText(ResponseTxt); 19 | 20 | Message(ResponseMsg, ResponseTxt); 21 | end; 22 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/13_AzureFunction/AzureFunctionSetup.Page.al: -------------------------------------------------------------------------------- 1 | page 54112 "MNB Azure Function Setup" 2 | { 3 | Caption = 'Azure Function Setup'; 4 | PageType = Card; 5 | SourceTable = "MNB Azure Function Setup"; 6 | ApplicationArea = All; 7 | UsageCategory = Administration; 8 | InsertAllowed = false; 9 | DeleteAllowed = false; 10 | 11 | layout 12 | { 13 | area(content) 14 | { 15 | group(General) 16 | { 17 | Caption = 'General'; 18 | field("Azure Function Url"; Rec."Azure Function Url") 19 | { 20 | ApplicationArea = All; 21 | ToolTip = 'Specifies the value of the Azure Function URL field.'; 22 | } 23 | } 24 | } 25 | } 26 | actions 27 | { 28 | area(Processing) 29 | { 30 | action(RunFunction) 31 | { 32 | Caption = 'Run Function'; 33 | Image = Start; 34 | ToolTip = 'Run Azure Function'; 35 | Promoted = true; 36 | PromotedCategory = Process; 37 | ApplicationArea = All; 38 | trigger OnAction() 39 | var 40 | MNBAzureFunctionMgt: Codeunit "MNB Azure Function Mgt"; 41 | begin 42 | MNBAzureFunctionMgt.RunAzureFunction(); 43 | end; 44 | } 45 | } 46 | } 47 | 48 | trigger OnOpenPage() 49 | begin 50 | if not Rec.Get() then begin 51 | Rec.Init(); 52 | Rec.Insert(); 53 | end; 54 | end; 55 | } 56 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/13_AzureFunction/AzureFunctionSetup.Table.al: -------------------------------------------------------------------------------- 1 | table 54103 "MNB Azure Function Setup" 2 | { 3 | DataClassification = SystemMetadata; 4 | Caption = 'Azure Function Setup'; 5 | 6 | fields 7 | { 8 | field(1; "Primary Key"; Code[10]) 9 | { 10 | Caption = 'Primary Key'; 11 | DataClassification = SystemMetadata; 12 | } 13 | field(2; "Azure Function Url"; Text[250]) 14 | { 15 | Caption = 'Azure Function Url'; 16 | DataClassification = SystemMetadata; 17 | ExtendedDatatype = Masked; 18 | } 19 | 20 | } 21 | 22 | keys 23 | { 24 | key(PK; "Primary Key") 25 | { 26 | Clustered = true; 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/14_Checklists/ChecklistInstall.Codeunit.al: -------------------------------------------------------------------------------- 1 | codeunit 54104 "MNB Checklist Install" 2 | { 3 | /// 4 | /// The examples in this codeunit show how to create Assisted Setup and Guided Experience (for example links to blog posts) 5 | /// 6 | /// 7 | Subtype = Install; 8 | trigger OnInstallAppPerCompany() 9 | begin 10 | CreateManualSetups(); 11 | CreateVideoLinks(); 12 | end; 13 | 14 | /// 15 | /// Create manual setups that can be later use in the checklists and are visible in the manual setups 16 | /// 17 | 18 | local procedure CreateManualSetups() 19 | var 20 | GuidedExperience: Codeunit "Guided Experience"; 21 | begin 22 | GuidedExperience.InsertManualSetup(AzureStorageTitleLbl, AzureStorageShortTitleLbl, AzureStorageDescriptionLbl, 2, ObjectType::Page, Page::"MNB Azure Storage Setup", Enum::"Manual Setup Category"::General, 'Demo'); 23 | GuidedExperience.InsertManualSetup(ImageAndCameraTitleLbl, ImageAndCameraShortTitleLbl, ImageAndCameraDescriptionLbl, 4, ObjectType::Page, Page::"Purchase Orders", Enum::"Manual Setup Category"::General, 'Demo'); 24 | GuidedExperience.InsertManualSetup(GeolocationTitleLbl, GeolocationShortTitleLbl, GeolocationDescriptionLbl, 4, ObjectType::Page, Page::"Fixed Asset List", Enum::"Manual Setup Category"::General, 'Demo'); 25 | GuidedExperience.InsertManualSetup(BarcodeTitleLbl, BarcodeShortTitleLbl, BarcodeDescriptionLbl, 4, ObjectType::Report, Report::"MNB Item Barcode", Enum::"Manual Setup Category"::General, 'Demo'); 26 | GuidedExperience.InsertManualSetup(FilterTokensTitleLbl, FilterTokensShortTitleLbl, FilterTokensDescriptionLbl, 4, ObjectType::Page, Page::"Sales Order List", Enum::"Manual Setup Category"::General, 'Demo'); 27 | GuidedExperience.InsertManualSetup(ObjectsTitleLbl, ObjectsShortTitleLbl, ObjectsDescriptionLbl, 4, ObjectType::Page, Page::"MNB Choosing Objects", Enum::"Manual Setup Category"::General, 'Demo'); 28 | GuidedExperience.InsertManualSetup(RecurrenceTitleLbl, RecurrenceShortTitleLbl, RecurrenceDescriptionLbl, 4, ObjectType::Page, Page::"MNB Next Occurrence", Enum::"Manual Setup Category"::General, 'Demo'); 29 | end; 30 | 31 | local procedure CreateVideoLinks() 32 | var 33 | GuidedExperience: Codeunit "Guided Experience"; 34 | begin 35 | GuidedExperience.InsertVideo('Special Guest', 'Special Guest', '', 1, 'https://www.youtube.com/embed/btPJPFnesV4', Enum::"Video Category"::DoMoreWithBC); 36 | end; 37 | 38 | 39 | 40 | var 41 | AzureStorageDescriptionLbl: Label 'Integration with Azure Blob storage and how to use it to import the files.'; 42 | AzureStorageShortTitleLbl: Label 'Azure Storage'; 43 | AzureStorageTitleLbl: Label 'Azure Blob Storage Example'; 44 | BarcodeDescriptionLbl: Label 'Print 1d and 2d barcode in the reports.'; 45 | BarcodeShortTitleLbl: Label 'Barcode Printouts'; 46 | BarcodeTitleLbl: Label 'Barcode Printouts Example'; 47 | FilterTokensDescriptionLbl: Label 'Filter tokens allow to easier filter the data with predefine tokens.'; 48 | FilterTokensShortTitleLbl: Label 'Filter Tokens'; 49 | FilterTokensTitleLbl: Label 'Filter Tokens Example'; 50 | GeolocationDescriptionLbl: Label 'Get GPS coordinates for the client.'; 51 | GeolocationShortTitleLbl: Label 'Geolocation'; 52 | GeolocationTitleLbl: Label 'Geolocation Example'; 53 | ImageAndCameraDescriptionLbl: Label 'Get pictures and process the images without additional code'; 54 | ImageAndCameraShortTitleLbl: Label 'Camera and Image'; 55 | ImageAndCameraTitleLbl: Label 'Camera and Image Example'; 56 | ObjectsDescriptionLbl: Label 'In SystemApp there are functions allowing to choose fields and objects.'; 57 | ObjectsShortTitleLbl: Label 'Choose Objects and Fields'; 58 | ObjectsTitleLbl: Label 'Choose Objects and Fields Example'; 59 | RecurrenceDescriptionLbl: Label 'Example shows how to get the next occurrence of date for specific schedule.'; 60 | RecurrenceShortTitleLbl: Label 'Recurrence Schedule'; 61 | RecurrenceTitleLbl: Label 'Recurrence Schedule Example'; 62 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/15_Environment/CompanyInformation.PagExt.al: -------------------------------------------------------------------------------- 1 | pageextension 54103 "MNB Company Information" extends "Company Information" 2 | { 3 | actions 4 | { 5 | addlast("Application Settings") 6 | { 7 | action(MNBEnvironmentInfo) 8 | { 9 | ApplicationArea = All; 10 | Image = Info; 11 | Caption = 'Environment Info'; 12 | Promoted = true; 13 | PromotedCategory = Process; 14 | trigger OnAction() 15 | var 16 | MNBEnvironmentInfo: Codeunit "MNB Environment Info"; 17 | begin 18 | MNBEnvironmentInfo.GetInfo(); 19 | end; 20 | } 21 | 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/15_Environment/EnvironmentInfo.Cod.al: -------------------------------------------------------------------------------- 1 | codeunit 54110 "MNB Environment Info" 2 | { 3 | procedure GetInfo() 4 | var 5 | EnvironmentInformation: Codeunit "Environment Information"; 6 | TenantInformation: Codeunit "Tenant Information"; 7 | InfoAboutEnvTxt: Label 'This is the information about the environment: \\ Is it Sandbox: %1 \\ Is it Production: %2 \\ Is it SaaS: %3 \\ Environment Name: %4'; 8 | InfoAboutTenantTxt: Label 'This is the information about the tenant: \\ Tenant ID: %1 \\ Tenant Display Name: %2'; 9 | begin 10 | Message(InfoAboutEnvTxt, 11 | EnvironmentInformation.IsSandbox(), 12 | EnvironmentInformation.IsProduction(), 13 | EnvironmentInformation.IsSaaS(), 14 | EnvironmentInformation.GetEnvironmentName()); 15 | 16 | Message(InfoAboutTenantTxt, 17 | TenantInformation.GetTenantID(), 18 | TenantInformation.GetTenantDisplayName()); 19 | end; 20 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/16_XMLWriter/CustomerList.PagExt.al: -------------------------------------------------------------------------------- 1 | pageextension 54104 "MNB Customer List" extends "Customer List" 2 | { 3 | actions 4 | { 5 | addlast("&Customer") 6 | { 7 | action(MNBExportToXML) 8 | { 9 | Caption = 'Export to XML'; 10 | ToolTip = 'Export to XML'; 11 | Image = Export; 12 | Promoted = true; 13 | PromotedCategory = Process; 14 | PromotedIsBig = true; 15 | PromotedOnly = true; 16 | ApplicationArea = All; 17 | trigger OnAction() 18 | var 19 | XMLExport: Codeunit "MNB XML Export"; 20 | begin 21 | XMLExport.ExportToXML(); 22 | end; 23 | } 24 | action(MNBExportToZip) 25 | { 26 | Caption = 'Export to Zip'; 27 | ToolTip = 'Export to Zip'; 28 | Image = Export; 29 | Promoted = true; 30 | PromotedCategory = Process; 31 | PromotedIsBig = true; 32 | PromotedOnly = true; 33 | ApplicationArea = All; 34 | trigger OnAction() 35 | var 36 | MNBZip: Codeunit "MNB Zip"; 37 | begin 38 | MNBZip.ExportToZip(); 39 | end; 40 | } 41 | } 42 | } 43 | 44 | 45 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/16_XMLWriter/XMLExport.Cod.al: -------------------------------------------------------------------------------- 1 | codeunit 54111 "MNB XML Export" 2 | { 3 | procedure ExportToXML() 4 | var 5 | MyXMLTxt: BigText; 6 | begin 7 | ExportCustomersToXML(MyXMLTxt); 8 | DownloadXML(MyXMLTxt); 9 | end; 10 | 11 | local procedure DownloadXML(var MyXMLTxt: BigText) 12 | var 13 | TempBlob: Codeunit "Temp Blob"; 14 | FileName: Text; 15 | FileNameTxt: Label 'Customers.xml'; 16 | OutStream: OutStream; 17 | InStream: InStream; 18 | begin 19 | TempBlob.CreateOutStream(OutStream); 20 | MyXMLTxt.Write(OutStream); 21 | TempBlob.CreateInStream(InStream); 22 | FileName := FileNameTxt; 23 | DownloadFromStream(InStream, '', '', '', FileName); 24 | end; 25 | 26 | procedure ExportCustomersToXML(var MyXMLTxt: BigText) 27 | var 28 | Customer: Record Customer; 29 | XmlWriter: Codeunit XmlWriter; 30 | begin 31 | XmlWriter.WriteStartDocument(); 32 | XmlWriter.WriteStartElement('Customers'); 33 | if Customer.FindSet() then 34 | repeat 35 | XmlWriter.WriteStartElement('Customer'); 36 | XmlWriter.WriteAttributeString('number', Customer."No."); 37 | XmlWriter.WriteElementString('name', Customer.Name); 38 | XmlWriter.WriteElementString('address', Customer.Address); 39 | XmlWriter.WriteEndElement; // Customer 40 | until Customer.Next() = 0; 41 | XmlWriter.WriteEndElement; // Customers 42 | XmlWriter.WriteEndDocument(); 43 | XmlWriter.ToBigText(MyXMLTxt); 44 | end; 45 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/17_Base64/Base64.Cod.al: -------------------------------------------------------------------------------- 1 | codeunit 54112 "MNB Base64" 2 | { 3 | procedure DownloadFromBase64(var Base64Txt: Text) 4 | var 5 | Base64Convert: Codeunit "Base64 Convert"; 6 | TempBlob: Codeunit "Temp Blob"; 7 | FileName: Text; 8 | FileNameTxt: Label 'Picture.jpg'; 9 | OutStream: OutStream; 10 | InStream: InStream; 11 | begin 12 | TempBlob.CreateOutStream(OutStream); 13 | Base64Convert.FromBase64(Base64Txt, OutStream); 14 | TempBlob.CreateInStream(InStream); 15 | FileName := FileNameTxt; 16 | DownloadFromStream(InStream, '', '', '', FileName); 17 | end; 18 | 19 | procedure PictureToBase64(): Text 20 | var 21 | Base64Convert: Codeunit "Base64 Convert"; 22 | FileName, Base64Text : Text; 23 | InStream: InStream; 24 | begin 25 | UploadIntoStream('', '', '', FileName, InStream); 26 | Base64Text := Base64Convert.ToBase64(InStream); 27 | exit(Base64Text); 28 | end; 29 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/17_Base64/Base64.Pag.al: -------------------------------------------------------------------------------- 1 | page 54113 "MNB Base64" 2 | { 3 | Caption = 'Base64 Example'; 4 | PageType = Card; 5 | ApplicationArea = All; 6 | UsageCategory = Administration; 7 | 8 | layout 9 | { 10 | area(Content) 11 | { 12 | group(Example) 13 | { 14 | Caption = 'Example'; 15 | field(Base64TextField; FromBase64Text) 16 | { 17 | ApplicationArea = All; 18 | Caption = 'Base64 Text'; 19 | MultiLine = true; 20 | ToolTip = 'Base64 Text value.'; 21 | } 22 | field(ImportedBase64TextField; ToBase64Text) 23 | { 24 | ApplicationArea = All; 25 | Caption = 'Imported Base64 Text'; 26 | MultiLine = true; 27 | ToolTip = 'Imported Base64 Text value.'; 28 | } 29 | 30 | } 31 | } 32 | } 33 | 34 | actions 35 | { 36 | area(Processing) 37 | { 38 | action(GetPicture) 39 | { 40 | ApplicationArea = All; 41 | Caption = 'Get Picture'; 42 | ToolTip = 'Get Picture from Base64 Text'; 43 | Promoted = true; 44 | PromotedCategory = Process; 45 | PromotedOnly = true; 46 | trigger OnAction() 47 | var 48 | MNBBase64: Codeunit "MNB Base64"; 49 | begin 50 | MNBBase64.DownloadFromBase64(FromBase64Text); 51 | end; 52 | } 53 | action(UploadPicture) 54 | { 55 | ApplicationArea = All; 56 | Caption = 'Upload Picture'; 57 | ToolTip = 'Upload Picture to Base64 Text'; 58 | Promoted = true; 59 | PromotedCategory = Process; 60 | PromotedOnly = true; 61 | trigger OnAction() 62 | var 63 | MNBBase64: Codeunit "MNB Base64"; 64 | begin 65 | ToBase64Text := MNBBase64.PictureToBase64(); 66 | end; 67 | } 68 | } 69 | } 70 | var 71 | FromBase64Text, ToBase64Text : Text; 72 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/18_Zip/Zip.Cod.al: -------------------------------------------------------------------------------- 1 | codeunit 54113 "MNB Zip" 2 | { 3 | 4 | procedure ExportToZip() 5 | var 6 | MNBXMLExport: Codeunit "MNB XML Export"; 7 | MyXMLTxt: BigText; 8 | begin 9 | MNBXMLExport.ExportCustomersToXML(MyXMLTxt); 10 | CreateZipFile(MyXMLTxt); 11 | end; 12 | 13 | local procedure CreateZipFile(var MyXMLTxt: BigText) 14 | var 15 | DataCompression: Codeunit "Data Compression"; 16 | TempBlob: Codeunit "Temp Blob"; 17 | FileName: Text; 18 | FileNameTxt: Label 'Customers.zip'; 19 | InStream, InStreamZip : InStream; 20 | OutStream, OutStreamZip : OutStream; 21 | begin 22 | DataCompression.CreateZipArchive(); 23 | 24 | //Add files 25 | TempBlob.CreateOutStream(OutStream); 26 | MyXMLTxt.Write(OutStream); 27 | TempBlob.CreateInStream(InStream); 28 | DataCompression.AddEntry(InStream, 'customers.xml'); 29 | DataCompression.AddEntry(InStream, 'customers_2.xml'); 30 | 31 | //Save zip file 32 | TempBlob.CreateOutStream(OutStreamZip); 33 | DataCompression.SaveZipArchive(OutStreamZip); 34 | TempBlob.CreateInStream(InStreamZip); 35 | FileName := FileNameTxt; 36 | DownloadFromStream(InStreamZip, '', '', '', FileName); 37 | end; 38 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/19_UserSelection/Customer.TabExt.al: -------------------------------------------------------------------------------- 1 | tableextension 54101 "MNB Customer" extends Customer 2 | { 3 | fields 4 | { 5 | field(54100; "MNB Responsible User"; Code[50]) 6 | { 7 | DataClassification = EndUserPseudonymousIdentifiers; 8 | Caption = 'Responsible User'; 9 | 10 | trigger OnValidate() 11 | var 12 | UserSelection: Codeunit "User Selection"; 13 | begin 14 | UserSelection.ValidateUserName(Rec."MNB Responsible User"); 15 | end; 16 | 17 | trigger OnLookup() 18 | var 19 | User: Record User; 20 | UserSelection: Codeunit "User Selection"; 21 | begin 22 | UserSelection.FilterSystemUserAndGroupUsers(User); 23 | UserSelection.Open(User); 24 | Rec."MNB Responsible User" := User."User Name"; 25 | end; 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/19_UserSelection/CustomerCard.PagExt.al: -------------------------------------------------------------------------------- 1 | pageextension 54105 "MNB Customer Card" extends "Customer Card" 2 | { 3 | layout 4 | { 5 | addlast(General) 6 | { 7 | field("MNB Responsible User"; Rec."MNB Responsible User") 8 | { 9 | ApplicationArea = All; 10 | ToolTip = 'Specifies the Responsible User for the customer.'; 11 | } 12 | } 13 | } 14 | actions 15 | { 16 | addlast("&Customer") 17 | { 18 | action(MNBEmailEditor) 19 | { 20 | ApplicationArea = All; 21 | Caption = 'Email Editor'; 22 | Image = Email; 23 | Promoted = true; 24 | PromotedCategory = Process; 25 | PromotedIsBig = true; 26 | ToolTip = 'Open email editor for the customer.'; 27 | 28 | trigger OnAction() 29 | var 30 | MNBEmail: Codeunit "MNB Email"; 31 | begin 32 | MNBEmail.OpenInEditor(Rec); 33 | end; 34 | } 35 | action(MNBEmailSend) 36 | { 37 | ApplicationArea = All; 38 | Caption = 'Email Send'; 39 | Image = Email; 40 | Promoted = true; 41 | PromotedCategory = Process; 42 | PromotedIsBig = true; 43 | ToolTip = 'Send email for the customer.'; 44 | 45 | trigger OnAction() 46 | var 47 | MNBEmail: Codeunit "MNB Email"; 48 | begin 49 | MNBEmail.SendEmail(Rec); 50 | end; 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/1_AzureStorage/AzureBlobStorage.Codeunit.al: -------------------------------------------------------------------------------- 1 | codeunit 54103 "MNB Azure Blob Storage" 2 | { 3 | /// 4 | /// The examples in this codeunit shows how to use Azure Blob Storage to get containers and files 5 | /// 6 | /// 7 | 8 | /// 9 | /// List all containers in the Azure Storage 10 | /// 11 | procedure ListContainers(): Text 12 | var 13 | ABSContainer: Record "ABS Container"; 14 | ABSContainerClient: Codeunit "ABS Container Client"; 15 | StorageServiceAuthorization: Codeunit "Storage Service Authorization"; 16 | Authorization: Interface "Storage Service Authorization"; 17 | begin 18 | MNBAzureStorageSetup.Get(); 19 | Authorization := StorageServiceAuthorization.CreateSharedKey(MNBAzureStorageSetup."Storage Key"); 20 | 21 | ABSContainerClient.Initialize(MNBAzureStorageSetup."Azure Account Name", Authorization); 22 | ABSContainerClient.ListContainers(ABSContainer); 23 | if Page.RunModal(Page::"MNB Azure Containers", ABSContainer) = Action::LookupOK then 24 | exit(ABSContainer.Name); 25 | end; 26 | 27 | /// 28 | /// List all files in the storage and download one 29 | /// 30 | procedure DownloadContainerFile(): Text 31 | var 32 | ABSContainerContent: Record "ABS Container Content"; 33 | ABSBlobClient: Codeunit "ABS Blob Client"; 34 | StorageServiceAuthorization: Codeunit "Storage Service Authorization"; 35 | Authorization: Interface "Storage Service Authorization"; 36 | begin 37 | MNBAzureStorageSetup.Get(); 38 | Authorization := StorageServiceAuthorization.CreateSharedKey(MNBAzureStorageSetup."Storage Key"); 39 | ABSBlobClient.Initialize(MNBAzureStorageSetup."Azure Account Name", MNBAzureStorageSetup."Container Name", Authorization); 40 | ABSBlobClient.ListBlobs(ABSContainerContent); 41 | if Page.RunModal(Page::"MNB Container Content", ABSContainerContent) = Action::LookupOK then 42 | ABSBlobClient.GetBlobAsFile(ABSContainerContent."Name") 43 | end; 44 | 45 | var 46 | MNBAzureStorageSetup: Record "MNB Azure Storage Setup"; 47 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/1_AzureStorage/AzureContainers.Page.al: -------------------------------------------------------------------------------- 1 | page 54104 "MNB Azure Containers" 2 | { 3 | Caption = 'MNB Azure Containers'; 4 | PageType = List; 5 | SourceTable = "ABS Container"; 6 | UsageCategory = None; 7 | Editable = false; 8 | InsertAllowed = false; 9 | DeleteAllowed = false; 10 | ModifyAllowed = false; 11 | 12 | layout 13 | { 14 | area(content) 15 | { 16 | repeater(General) 17 | { 18 | field(Name; Rec.Name) 19 | { 20 | ApplicationArea = All; 21 | ToolTip = 'Specifies the value of the Name field.'; 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/1_AzureStorage/AzureStorageSetup.Page.al: -------------------------------------------------------------------------------- 1 | page 54102 "MNB Azure Storage Setup" 2 | { 3 | Caption = 'MNB Azure Storage Setup'; 4 | PageType = Card; 5 | SourceTable = "MNB Azure Storage Setup"; 6 | ApplicationArea = All; 7 | UsageCategory = Administration; 8 | InsertAllowed = false; 9 | DeleteAllowed = false; 10 | 11 | layout 12 | { 13 | area(content) 14 | { 15 | group(General) 16 | { 17 | Caption = 'General'; 18 | field("Container Name"; Rec."Container Name") 19 | { 20 | ApplicationArea = All; 21 | ToolTip = 'Specifies the value of the Container Name field.'; 22 | Editable = false; 23 | } 24 | field("Azure Account Name"; Rec."Azure Account Name") 25 | { 26 | ToolTip = 'Specifies the value of the Azure Account Name field.'; 27 | ApplicationArea = All; 28 | Importance = Additional; 29 | } 30 | field("Storage Key"; Rec."Storage Key") 31 | { 32 | ToolTip = 'Specifies the value of the Storage Key field.'; 33 | ApplicationArea = All; 34 | Importance = Additional; 35 | } 36 | } 37 | } 38 | } 39 | actions 40 | { 41 | area(Processing) 42 | { 43 | action(ListContainers) 44 | { 45 | Caption = 'List Containers'; 46 | Image = ShowList; 47 | ToolTip = 'Show list of containers'; 48 | Promoted = true; 49 | PromotedCategory = Process; 50 | ApplicationArea = All; 51 | trigger OnAction() 52 | var 53 | MNBAzureBlobStorage: Codeunit "MNB Azure Blob Storage"; 54 | begin 55 | Rec."Container Name" := MNBAzureBlobStorage.ListContainers(); 56 | end; 57 | } 58 | action(ListFiles) 59 | { 60 | Caption = 'List Files'; 61 | Image = FileContract; 62 | ToolTip = 'Show list of files in the container.'; 63 | Promoted = true; 64 | PromotedCategory = Process; 65 | ApplicationArea = All; 66 | trigger OnAction() 67 | var 68 | MNBAzureBlobStorage: Codeunit "MNB Azure Blob Storage"; 69 | begin 70 | MNBAzureBlobStorage.DownloadContainerFile(); 71 | end; 72 | } 73 | } 74 | } 75 | 76 | trigger OnOpenPage() 77 | begin 78 | if not Rec.Get() then begin 79 | Rec.Init(); 80 | Rec.Insert(); 81 | end; 82 | end; 83 | } 84 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/1_AzureStorage/AzureStorageSetup.Table.al: -------------------------------------------------------------------------------- 1 | table 54100 "MNB Azure Storage Setup" 2 | { 3 | DataClassification = SystemMetadata; 4 | Caption = 'Azure Storage Setup'; 5 | 6 | fields 7 | { 8 | field(1; "Primary Key"; Code[10]) 9 | { 10 | Caption = 'Primary Key'; 11 | DataClassification = SystemMetadata; 12 | } 13 | field(2; "Container Name"; Text[250]) 14 | { 15 | Caption = 'Container Name'; 16 | DataClassification = SystemMetadata; 17 | } 18 | field(3; "Azure Account Name"; Text[250]) 19 | { 20 | Caption = 'Azure Account Name'; 21 | DataClassification = SystemMetadata; 22 | } 23 | field(4; "Storage Key"; Text[1024]) 24 | { 25 | Caption = 'Storage Key'; 26 | DataClassification = SystemMetadata; 27 | ExtendedDatatype = Masked; 28 | } 29 | 30 | } 31 | 32 | keys 33 | { 34 | key(PK; "Primary Key") 35 | { 36 | Clustered = true; 37 | } 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/1_AzureStorage/ContainerContent.Page.al: -------------------------------------------------------------------------------- 1 | page 54103 "MNB Container Content" 2 | { 3 | Caption = 'MNB Container Content'; 4 | PageType = List; 5 | SourceTable = "ABS Container Content"; 6 | UsageCategory = None; 7 | Editable = false; 8 | InsertAllowed = false; 9 | DeleteAllowed = false; 10 | ModifyAllowed = false; 11 | 12 | layout 13 | { 14 | area(content) 15 | { 16 | repeater(General) 17 | { 18 | field(Name; Rec.Name) 19 | { 20 | ApplicationArea = All; 21 | ToolTip = 'Specifies the value of the Name field.'; 22 | } 23 | 24 | field("Content Type"; Rec."Content Type") 25 | { 26 | ApplicationArea = All; 27 | ToolTip = 'Specifies the value of the Content-Type field.'; 28 | } 29 | field("Creation Time"; Rec."Creation Time") 30 | { 31 | ApplicationArea = All; 32 | ToolTip = 'Specifies the value of the Creation Time field.'; 33 | } 34 | field("Full Name"; Rec."Full Name") 35 | { 36 | ApplicationArea = All; 37 | ToolTip = 'Specifies the value of the Full Name field.'; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/20_Email/Email.Cod.al: -------------------------------------------------------------------------------- 1 | codeunit 54114 "MNB Email" 2 | { 3 | /// 4 | /// The example how to open and send email from the customer card 5 | /// To test scenario you need to setup the email account in the system 6 | /// 7 | /// 8 | 9 | procedure OpenInEditor(var Customer: Record Customer) 10 | var 11 | Email: Codeunit Email; 12 | EmailMessage: Codeunit "Email Message"; 13 | EmailSubjectTxt: Label 'Please Pay!'; 14 | EmailBodyTxt: Label 'Hi, Please pay your invoices!'; 15 | begin 16 | EmailMessage.Create(Customer."E-Mail", EmailSubjectTxt, EmailBodyTxt, true); 17 | Email.OpenInEditor(EmailMessage, Enum::"Email Scenario"::"MNB My Email Scenario"); 18 | end; 19 | 20 | procedure SendEmail(var Customer: Record Customer) 21 | var 22 | Email: Codeunit Email; 23 | EmailMessage: Codeunit "Email Message"; 24 | EmailSubjectTxt: Label 'Please Pay!'; 25 | EmailBodyTxt: Label 'Hi, Please pay your invoices!'; 26 | begin 27 | EmailMessage.Create(Customer."E-Mail", EmailSubjectTxt, EmailBodyTxt, true); 28 | Email.Send(EmailMessage, Enum::"Email Scenario"::"MNB My Email Scenario"); 29 | end; 30 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/20_Email/EmailScenario.EnumExt.al: -------------------------------------------------------------------------------- 1 | enumextension 54100 "MNB Email Scenario" extends "Email Scenario" 2 | { 3 | value(54100; "MNB My Email Scenario") 4 | { 5 | Caption = 'My Email Scenario'; 6 | } 7 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/21_Blob/PersistentBlob.Cod.al: -------------------------------------------------------------------------------- 1 | codeunit 54115 "MNB Persistent Blob" 2 | { 3 | procedure SaveBlobToPersistedBlob(): BigInteger 4 | var 5 | PersistentBlob: Codeunit "Persistent Blob"; 6 | BlobInteger: BigInteger; 7 | InStream: InStream; 8 | FileName: Text; 9 | begin 10 | UploadIntoStream('', '', '', FileName, InStream); 11 | BlobInteger := PersistentBlob.Create(); 12 | PersistentBlob.CopyFromInStream(BlobInteger, InStream); 13 | exit(BlobInteger); 14 | end; 15 | 16 | procedure DownloadBlobFromPersistedBlob(BlobInteger: BigInteger) 17 | var 18 | PersistentBlob: Codeunit "Persistent Blob"; 19 | TempBlob: Codeunit "Temp Blob"; 20 | InStream: InStream; 21 | BlobDoesNotExistErr: Label 'Blob does not exist'; 22 | FileNameTxt: Label 'ExportedFile.txt'; 23 | OutStream: OutStream; 24 | FileName: Text; 25 | 26 | begin 27 | if not PersistentBlob.Exists(BlobInteger) then 28 | Error(BlobDoesNotExistErr); 29 | TempBlob.CreateOutStream(OutStream); 30 | PersistentBlob.CopyToOutStream(BlobInteger, OutStream); 31 | TempBlob.CreateInStream(InStream); 32 | FileName := FileNameTxt; 33 | DownloadFromStream(InStream, '', '', '', FileName); 34 | end; 35 | 36 | procedure DeleteBlobFromPersistedBlob(BlobInteger: BigInteger) 37 | var 38 | PersistentBlob: Codeunit "Persistent Blob"; 39 | begin 40 | if PersistentBlob.Exists(BlobInteger) then 41 | PersistentBlob.Delete(BlobInteger); 42 | end; 43 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/21_Blob/PersistentBlob.Pag.al: -------------------------------------------------------------------------------- 1 | page 54114 "MNB Persistent Blob" 2 | { 3 | Caption = 'MNB Persistent Blob'; 4 | PageType = Card; 5 | ApplicationArea = All; 6 | UsageCategory = Administration; 7 | 8 | layout 9 | { 10 | area(Content) 11 | { 12 | group(GroupName) 13 | { 14 | Caption = 'Persistent Blob'; 15 | field(BlobIntegerField; BlobInteger) 16 | { 17 | ApplicationArea = All; 18 | Caption = 'Blob Integer Field'; 19 | } 20 | } 21 | } 22 | } 23 | 24 | actions 25 | { 26 | area(Processing) 27 | { 28 | action(ImportBlob) 29 | { 30 | ApplicationArea = All; 31 | Caption = 'Import Blob'; 32 | Promoted = true; 33 | PromotedCategory = Process; 34 | Image = Import; 35 | trigger OnAction() 36 | var 37 | MNBPersistentBlob: Codeunit "MNB Persistent Blob"; 38 | begin 39 | BlobInteger := MNBPersistentBlob.SaveBlobToPersistedBlob() 40 | end; 41 | } 42 | action(ExportBlob) 43 | { 44 | ApplicationArea = All; 45 | Caption = 'Export Blob'; 46 | Promoted = true; 47 | PromotedCategory = Process; 48 | Image = Export; 49 | trigger OnAction() 50 | var 51 | MNBPersistentBlob: Codeunit "MNB Persistent Blob"; 52 | begin 53 | MNBPersistentBlob.DownloadBlobFromPersistedBlob(BlobInteger); 54 | end; 55 | } 56 | 57 | action(DeleteBlob) 58 | { 59 | ApplicationArea = All; 60 | Caption = 'Delete Blob'; 61 | Promoted = true; 62 | PromotedCategory = Process; 63 | Image = Delete; 64 | trigger OnAction() 65 | var 66 | MNBPersistentBlob: Codeunit "MNB Persistent Blob"; 67 | begin 68 | MNBPersistentBlob.DeleteBlobFromPersistedBlob(BlobInteger); 69 | end; 70 | } 71 | 72 | } 73 | } 74 | var 75 | BlobInteger: BigInteger; 76 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/2_ImageCamera/CameraandImage.Cod.al: -------------------------------------------------------------------------------- 1 | codeunit 54101 "MNB Camera and Image" 2 | { 3 | /// 4 | /// The example how to take photo from the camera in few lines of code 5 | /// 6 | /// 7 | 8 | internal procedure TakePurchaseInvoicePhoto(var PurchaseHeader: Record "Purchase Header") 9 | var 10 | Camera: Codeunit Camera; 11 | PictureInStream: InStream; 12 | CameraIsNotAvailableErr: Label 'Camera is not available.'; 13 | NoPhotoTakenErr: Label 'No Photo Taken'; 14 | PictureText: Text; 15 | begin 16 | if not Camera.IsAvailable() then 17 | Error(CameraIsNotAvailableErr); 18 | 19 | if not Camera.GetPicture(PictureInStream, PictureText) then 20 | Error(NoPhotoTakenErr); 21 | 22 | SavePicture(PurchaseHeader, PictureInStream, PictureText); 23 | end; 24 | 25 | internal procedure GetPictureSizeAndTransform(var PurchaseHeader: Record "Purchase Header") 26 | var 27 | Camera: Codeunit Camera; 28 | Image: Codeunit Image; 29 | PictureInStream: InStream; 30 | CameraIsNotAvailableErr: Label 'Camera is not available.'; 31 | NoPhotoTakenErr: Label 'No Photo Taken'; 32 | PictureSizeMsg: Label 'Picture Size: \ Height: %1\ Width: %2.'; 33 | PictureText: Text; 34 | begin 35 | if not Camera.IsAvailable() then 36 | Error(CameraIsNotAvailableErr); 37 | 38 | if not Camera.GetPicture(PictureInStream, PictureText) then 39 | Error(NoPhotoTakenErr); 40 | 41 | TransformImage(Image, PictureInStream); 42 | 43 | GetTransformedFile(Image, PurchaseHeader); 44 | 45 | Message(PictureSizeMsg, Image.GetHeight(), Image.GetWidth()); 46 | 47 | end; 48 | 49 | local procedure SavePicture(var PurchaseHeader: Record "Purchase Header"; var PictureInStream: InStream; var PictureText: Text) 50 | var 51 | MimeTypeTok: Label 'image/jpeg', Locked = true; 52 | begin 53 | Clear(PurchaseHeader."MNB Invoice Picture"); 54 | PurchaseHeader."MNB Invoice Picture".ImportStream(PictureInStream, PictureText, MimeTypeTok); 55 | PurchaseHeader.Modify(true); 56 | end; 57 | 58 | local procedure GetTransformedFile(var Image: Codeunit Image; var PurchaseHeader: Record "Purchase Header") 59 | var 60 | TempBlob: Codeunit "Temp Blob"; 61 | InStream: InStream; 62 | MimeTypeTok: Label 'image/jpeg', Locked = true; 63 | PictureTxt: Label 'picture_name.jpg'; 64 | PictureOutStream: OutStream; 65 | begin 66 | TempBlob.CreateOutStream(PictureOutStream); 67 | Image.Save(PictureOutStream); 68 | TempBlob.CreateInStream(InStream); 69 | Clear(PurchaseHeader."MNB Invoice Picture"); 70 | PurchaseHeader."MNB Invoice Picture".ImportStream(InStream, PictureTxt, MimeTypeTok); 71 | PurchaseHeader.Modify(true); 72 | end; 73 | 74 | local procedure TransformImage(var Image: Codeunit Image; var PictureInStream: InStream) 75 | begin 76 | Image.FromStream(PictureInStream); 77 | Image.RotateFlip(Enum::"Rotate Flip Type"::Rotate90FlipNone); 78 | Image.Crop(0, 0, Image.GetWidth() - 100, Image.GetHeight() - 100); 79 | end; 80 | 81 | procedure Example() 82 | var 83 | Image: Codeunit Image; 84 | TempBlob: Codeunit "Temp Blob"; 85 | InStream: InStream; 86 | OutStream: OutStream; 87 | FileName: Text; 88 | begin 89 | UploadIntoStream('', '', '', FileName, InStream); 90 | 91 | Image.FromStream(InStream); 92 | Image.SetFormat(Enum::"Image Format"::Png); 93 | 94 | TempBlob.CreateOutStream(OutStream); 95 | Image.Save(OutStream); 96 | 97 | TempBlob.CreateInStream(InStream); 98 | FileName := FileName + '.png'; 99 | DownloadFromStream(InStream, '', '', '', FileName); 100 | end; 101 | 102 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/2_ImageCamera/PurchaseHeader.TableExt.al: -------------------------------------------------------------------------------- 1 | tableextension 54100 "MNB Purchase Header" extends "Purchase Header" 2 | { 3 | fields 4 | { 5 | field(54100; "MNB Invoice Picture"; Media) 6 | { 7 | Caption = 'MNB Invoice Picture'; 8 | DataClassification = SystemMetadata; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/2_ImageCamera/PurchaseOrder.PagExt.al: -------------------------------------------------------------------------------- 1 | pageextension 54101 "MNB Purchase Order" extends "Purchase Order" 2 | { 3 | layout 4 | { 5 | addfirst(factboxes) 6 | { 7 | part(MNBPicture; "MNB Purchase Order Picture") 8 | { 9 | ApplicationArea = All; 10 | Caption = 'Picture'; 11 | SubPageLink = "No." = field("No."); 12 | } 13 | } 14 | } 15 | actions 16 | { 17 | addlast(IncomingDocument) 18 | { 19 | action(MNBTakeInvoicePhoto) 20 | { 21 | Caption = 'Take Invoice Photo'; 22 | ToolTip = 'Allows to take the invoice photo before invoice'; 23 | Promoted = true; 24 | PromotedCategory = Process; 25 | PromotedIsBig = true; 26 | Image = Camera; 27 | ApplicationArea = All; 28 | Enabled = CameraIsAvailable; 29 | trigger OnAction() 30 | var 31 | MNBCameraImage: Codeunit "MNB Camera and Image"; 32 | begin 33 | MNBCameraImage.TakePurchaseInvoicePhoto(Rec); 34 | end; 35 | } 36 | action(MNBTakeInvoicePhotoSize) 37 | { 38 | Caption = 'Invoice Photo (Size)'; 39 | ToolTip = 'Allows to check size of the photo.'; 40 | Promoted = true; 41 | PromotedCategory = Process; 42 | PromotedIsBig = true; 43 | Image = Camera; 44 | ApplicationArea = All; 45 | Enabled = CameraIsAvailable; 46 | trigger OnAction() 47 | var 48 | MNBCameraImage: Codeunit "MNB Camera and Image"; 49 | begin 50 | MNBCameraImage.GetPictureSizeAndTransform(Rec); 51 | end; 52 | } 53 | 54 | } 55 | } 56 | trigger OnOpenPage() 57 | var 58 | Camera: Codeunit Camera; 59 | begin 60 | CameraIsAvailable := Camera.IsAvailable(); 61 | end; 62 | 63 | trigger OnAfterGetCurrRecord() 64 | begin 65 | 66 | end; 67 | 68 | var 69 | CameraIsAvailable: Boolean; 70 | } 71 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/2_ImageCamera/PurchaseOrderPicture.Pag.al: -------------------------------------------------------------------------------- 1 | page 54100 "MNB Purchase Order Picture" 2 | { 3 | Caption = 'MNB Purchase Order Picture'; 4 | PageType = CardPart; 5 | SourceTable = "Purchase Header"; 6 | ApplicationArea = All; 7 | 8 | layout 9 | { 10 | area(content) 11 | { 12 | group(General) 13 | { 14 | ShowCaption = false; 15 | field("MNB Invoice Picture"; Rec."MNB Invoice Picture") 16 | { 17 | ApplicationArea = All; 18 | ToolTip = 'Shows the invoice picture for the purchase order'; 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/3_GeoLocation/AssetLocationMgt.Codeunit.al: -------------------------------------------------------------------------------- 1 | codeunit 54102 "MNB Asset Location Mgt" 2 | { 3 | /// 4 | /// The example how to get GPS coordinates 5 | /// 6 | /// 7 | internal procedure GetGeolocation(FixedAsset: Record "Fixed Asset") 8 | var 9 | Geolocation: Codeunit Geolocation; 10 | Latitude: Decimal; 11 | Longitude: Decimal; 12 | FixedAssetIsLocatedMsg: Label 'Fixed Asset %1 is located at: \ Latitude: %2 \ Longitude: %3', Comment = '%1 = FA description, %2 - Lat. value, %3 - Long. value'; 13 | begin 14 | Geolocation.SetHighAccuracy(true); 15 | if Geolocation.RequestGeolocation() then 16 | Geolocation.GetGeolocation(Latitude, Longitude); 17 | Message(FixedAssetIsLocatedMsg, FixedAsset."Description", Latitude, Longitude); 18 | end; 19 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/3_GeoLocation/FixedAssetCard.PageExt.al: -------------------------------------------------------------------------------- 1 | pageextension 54102 "MNB Fixed Asset Card" extends "Fixed Asset Card" 2 | { 3 | actions 4 | { 5 | addlast(navigation) 6 | { 7 | action(MNBGetCoordinates) 8 | { 9 | Caption = 'Get Coordinates'; 10 | ToolTip = 'Shows GPS Coordinates for the fixed asset'; 11 | Promoted = true; 12 | PromotedCategory = Process; 13 | PromotedIsBig = true; 14 | Image = Map; 15 | ApplicationArea = All; 16 | trigger OnAction() 17 | var 18 | AssetLocationMgt: Codeunit "MNB Asset Location Mgt"; 19 | begin 20 | AssetLocationMgt.GetGeolocation(Rec); 21 | end; 22 | 23 | } 24 | } 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/4_Barcode/ItemBarcode.Report.al: -------------------------------------------------------------------------------- 1 | report 54100 "MNB Item Barcode" 2 | { 3 | /// 4 | /// Shows how to create barcodes for item 5 | /// 6 | /// 7 | /// 8 | /// 9 | 10 | ApplicationArea = All; 11 | Caption = 'Item Barcode'; 12 | UsageCategory = ReportsAndAnalysis; 13 | WordLayout = './src/Examples/4_Barcode/ItemBarcode.docx'; 14 | DefaultLayout = Word; 15 | dataset 16 | { 17 | dataitem(Item; Item) 18 | { 19 | RequestFilterFields = "No."; 20 | column(No; "No.") 21 | { 22 | } 23 | column(Description; Description) 24 | { 25 | } 26 | column(Ean13Txt; Ean13Txt) { } 27 | column(Code39Txt; Code39Txt) { } 28 | column(Code128Txt; Code128Txt) { } 29 | column(QRTxt; QRTxt) { } 30 | column(AztecTxt; AztecTxt) { } 31 | 32 | trigger OnAfterGetRecord() 33 | var 34 | BarcodeFontProvider: Interface "Barcode Font Provider"; 35 | BarcodeFontProvider2D: Interface "Barcode Font Provider 2D"; 36 | begin 37 | BarCodeTxt := '978020137962'; 38 | BarcodeFontProvider := Enum::"Barcode Font Provider"::IDAutomation1D; 39 | Ean13Txt := BarcodeFontProvider.EncodeFont(BarCodeTxt, Enum::"Barcode Symbology"::"EAN-13"); 40 | Code39Txt := BarcodeFontProvider.EncodeFont(Item."No.", Enum::"Barcode Symbology"::Code39); 41 | Code128Txt := BarcodeFontProvider.EncodeFont(StrSubstNo('%1 %2', Item."No.", Item.Description), Enum::"Barcode Symbology"::Code128); 42 | 43 | BarcodeFontProvider2D := Enum::"Barcode Font Provider 2D"::IDAutomation2D; 44 | BarCodeTxt := 'URL:https://www.mynavblog.com'; 45 | QRTxt := BarcodeFontProvider2D.EncodeFont(BarCodeTxt, Enum::"Barcode Symbology 2D"::"QR-Code"); 46 | AztecTxt := BarcodeFontProvider2D.EncodeFont(BarCodeTxt, Enum::"Barcode Symbology 2D"::Aztec); 47 | end; 48 | } 49 | } 50 | var 51 | AztecTxt, Code39Txt, Code128Txt, Ean13Txt, QRTxt : Text; 52 | BarCodeTxt: Text; 53 | } 54 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/4_Barcode/ItemBarcode.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mynavblog/SystemAppExamples/170845c075b3dd87cfb19e06efd3d7d111b2f4c2/SystemAppPresentation/src/Examples/4_Barcode/ItemBarcode.docx -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/5_FilterTokens/FilterTokens.Codeunit.al: -------------------------------------------------------------------------------- 1 | codeunit 54100 "MNB Filter Tokens" 2 | { 3 | /// 4 | /// The examples in this codeunit shows how to create Filter Tokens 5 | /// 6 | /// 7 | 8 | /// 9 | /// Add new Filter Tokens fro Date filters 10 | /// 11 | [EventSubscriber(ObjectType::Codeunit, Codeunit::"Filter Tokens", 'OnResolveDateFilterToken', '', true, true)] 12 | local procedure RunOnResolveDateFilterToken(DateToken: Text; var FromDate: Date; var ToDate: Date; var Handled: Boolean) 13 | begin 14 | AddNextLastWeekFilterToken(DateToken, FromDate, ToDate, Handled); 15 | end; 16 | 17 | /// 18 | /// Example for Filter Token Text 19 | /// 20 | local procedure FilterTokensTextExamples() 21 | begin 22 | 23 | end; 24 | 25 | local procedure AddNextLastWeekFilterToken(DateToken: Text; var FromDate: Date; var ToDate: Date; var Handled: Boolean) 26 | var 27 | LastWeekLbl: Label 'LASTWEEK', Comment = 'Must be uppercase'; 28 | NextWeekLbl: Label 'NEXTWEEK', Comment = 'Must be uppercase'; 29 | begin 30 | case DateToken.ToUpper() of 31 | NextWeekLbl: 32 | begin 33 | FromDate := Today; 34 | ToDate := CalcDate('<7D>', Today); 35 | Handled := true; 36 | end; 37 | LastWeekLbl: 38 | begin 39 | FromDate := CalcDate('<-7D>', Today); 40 | ToDate := Today; 41 | Handled := true; 42 | end; 43 | end; 44 | end; 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/5_FilterTokens/SalesOrderList.PageExt.al: -------------------------------------------------------------------------------- 1 | pageextension 54100 "MNB Sales Order List" extends "Sales Order List" 2 | { 3 | views 4 | { 5 | addlast 6 | { 7 | view(MNBNextWeek) 8 | { 9 | Caption = 'Next Week Orders'; 10 | Filters = where("Requested Delivery Date" = filter('%nextweek')); 11 | SharedLayout = true; 12 | } 13 | view(MNBLastWeek) 14 | { 15 | Caption = 'Last Week Orders'; 16 | Filters = where("Requested Delivery Date" = filter('%lastweek')); 17 | SharedLayout = true; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/6_RetentionPolicy/MyLog.Table.al: -------------------------------------------------------------------------------- 1 | table 54101 "MNB My Log Table" 2 | { 3 | DataClassification = SystemMetadata; 4 | Caption = 'My Log Table'; 5 | 6 | fields 7 | { 8 | field(1; "Entry No."; Integer) 9 | { 10 | DataClassification = SystemMetadata; 11 | Caption = 'Entry No.'; 12 | } 13 | field(2; "Description"; Text[100]) 14 | { 15 | DataClassification = SystemMetadata; 16 | Caption = 'Description'; 17 | } 18 | } 19 | 20 | keys 21 | { 22 | key(Key1; "Entry No.") 23 | { 24 | Clustered = true; 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/6_RetentionPolicy/RetenPolInstall.Codeunit.al: -------------------------------------------------------------------------------- 1 | codeunit 54105 "MNB Reten. Pol. Install" 2 | { 3 | /// 4 | /// Shows how to add tables to retention policy allowed tables 5 | /// 6 | /// 7 | Subtype = Install; 8 | 9 | trigger OnInstallAppPerCompany() 10 | var 11 | RetenPolAllowedTables: Codeunit "Reten. Pol. Allowed Tables"; 12 | begin 13 | RetenPolAllowedTables.AddAllowedTable(Database::"MNB My Log Table") 14 | end; 15 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/7_ChoosingObjects/ChoosingObjects.Pag.al: -------------------------------------------------------------------------------- 1 | page 54105 "MNB Choosing Objects" 2 | { 3 | PageType = Card; 4 | ApplicationArea = All; 5 | UsageCategory = Administration; 6 | Caption = 'MNB Choosing Objects'; 7 | 8 | layout 9 | { 10 | area(Content) 11 | { 12 | group(GroupName) 13 | { 14 | Caption = 'Object'; 15 | field(ObjectNameField; ObjectName) 16 | { 17 | ApplicationArea = All; 18 | ToolTip = 'Specifies the object name. Choose the object from assist edit.'; 19 | Editable = false; 20 | Caption = 'Object Name'; 21 | trigger OnAssistEdit() 22 | var 23 | AllObjWithCaption: Record AllObjWithCaption; 24 | ObjectAndFields: Codeunit "MNB Object and Fields"; 25 | begin 26 | if ObjectAndFields.SelectObject(AllObjWithCaption) then 27 | ObjectName := AllObjWithCaption."Object Name"; 28 | end; 29 | } 30 | field(FieldNameField; FieldName) 31 | { 32 | ApplicationArea = All; 33 | ToolTip = 'Specifies the field name. Choose the field from table Items from assist edit.'; 34 | Editable = false; 35 | Caption = 'Field Name'; 36 | trigger OnAssistEdit() 37 | var 38 | ObjectAndFields: Codeunit "MNB Object and Fields"; 39 | begin 40 | ObjectAndFields.GetFieldCaption(FieldName); 41 | end; 42 | } 43 | } 44 | } 45 | } 46 | 47 | var 48 | FieldName, ObjectName : Text; 49 | 50 | 51 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/7_ChoosingObjects/ObjectAndFields.Codeunit.al: -------------------------------------------------------------------------------- 1 | codeunit 54106 "MNB Object and Fields" 2 | { 3 | /// 4 | /// Shows how to choose objects 5 | /// 6 | /// 7 | procedure SelectObject(var Result: Record AllObjWithCaption): Boolean 8 | var 9 | AllObjects: Record AllObjWithCaption; 10 | Objects: Page Objects; 11 | begin 12 | AllObjects.FilterGroup(2); 13 | AllObjects.SetRange("Object Type", AllObjects."Object Type"::Table); 14 | AllObjects.FilterGroup(0); 15 | 16 | Objects.SetRecord(AllObjects); 17 | Objects.SetTableView(AllObjects); 18 | Objects.LookupMode := true; 19 | 20 | if Objects.RunModal = Action::LookupOK then begin 21 | Objects.GetRecord(Result); 22 | exit(true); 23 | end; 24 | 25 | exit(false); 26 | end; 27 | 28 | /// 29 | /// Shows how to choose fields 30 | /// 31 | /// 32 | 33 | procedure GetFieldCaption(var FieldCaption: Text) 34 | var 35 | SelectedField: Record Field; 36 | FieldSelection: Codeunit "Field Selection"; 37 | begin 38 | SelectedField.FilterGroup(2); 39 | SelectedField.SetRange(TableNo, 27); 40 | SelectedField.FilterGroup(0); 41 | if FieldSelection.Open(SelectedField) then 42 | FieldCaption := SelectedField."Field Caption"; 43 | end; 44 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/8_RecurranceSchedule/NextOccurrence.Pag.al: -------------------------------------------------------------------------------- 1 | page 54106 "MNB Next Occurrence" 2 | { 3 | PageType = Card; 4 | ApplicationArea = All; 5 | UsageCategory = Administration; 6 | Caption = 'Next Occurrence'; 7 | 8 | layout 9 | { 10 | area(Content) 11 | { 12 | group(GroupName) 13 | { 14 | Caption = 'Next Occurrence'; 15 | field(Name; RecurrenceID) 16 | { 17 | ApplicationArea = All; 18 | ToolTip = 'Specifies set recurrence.Use Assist Edit to set it.'; 19 | Editable = false; 20 | trigger OnAssistEdit() 21 | var 22 | OccurrenceMgt: Codeunit "MNB Occurrence Mgt."; 23 | begin 24 | RecurrenceText := OccurrenceMgt.OpenRecurrenceScheduleCard(RecurrenceID); 25 | end; 26 | } 27 | field(RecurrenceTextField; RecurrenceText) 28 | { 29 | Caption = 'Recurrence Description'; 30 | ToolTip = 'Specifies the description for the recurrence schedule.'; 31 | Editable = false; 32 | } 33 | 34 | field(LastOccurrenceDateField; LastOccurrenceDateTime) 35 | { 36 | Caption = 'Last Occurrence Date-Time'; 37 | ToolTip = 'Specifies the last occurrence of the schedule.'; 38 | } 39 | } 40 | } 41 | } 42 | actions 43 | { 44 | area(Processing) 45 | { 46 | action(GetNextDate) 47 | { 48 | Caption = 'Get Next Date-Time'; 49 | Image = NextRecord; 50 | ToolTip = 'Get next occurrence of the schedule.'; 51 | Promoted = true; 52 | PromotedCategory = Process; 53 | trigger OnAction() 54 | var 55 | OccurrenceMgt: Codeunit "MNB Occurrence Mgt."; 56 | begin 57 | OccurrenceMgt.ShowNextDate(RecurrenceID, LastOccurrenceDateTime); 58 | end; 59 | } 60 | } 61 | } 62 | 63 | 64 | 65 | var 66 | LastOccurrenceDateTime: DateTime; 67 | RecurrenceID: Guid; 68 | RecurrenceText: Text; 69 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/8_RecurranceSchedule/OccurrenceMgt.Codeunit.al: -------------------------------------------------------------------------------- 1 | /// 2 | /// Shows how to get next occurrence for specific schedule 3 | /// 4 | /// 5 | codeunit 54107 "MNB Occurrence Mgt." 6 | { 7 | procedure OpenRecurrenceScheduleCard(var RecurrenceID: Guid): Text 8 | var 9 | RecurrenceSchedule: Codeunit "Recurrence Schedule"; 10 | begin 11 | RecurrenceSchedule.OpenRecurrenceSchedule(RecurrenceID); 12 | exit(RecurrenceSchedule.RecurrenceDisplayText(RecurrenceID)); 13 | end; 14 | 15 | procedure ShowNextDate(RecurrenceID: Guid; LastDate: DateTime) 16 | var 17 | RecurrenceSchedule: Codeunit "Recurrence Schedule"; 18 | NextOccurrenceTxt: Label 'Next Occurrence: %1', Comment = '%1 - date-time'; 19 | begin 20 | Message(NextOccurrenceTxt, RecurrenceSchedule.CalculateNextOccurrence(RecurrenceID, LastDate)); 21 | end; 22 | 23 | } -------------------------------------------------------------------------------- /SystemAppPresentation/src/Examples/9_Math/MathOperations.Page.al: -------------------------------------------------------------------------------- 1 | page 54101 "MNB Math Operations" 2 | { 3 | /// 4 | /// Shows example of math module 5 | /// 6 | /// 7 | 8 | Caption = 'Math Operations'; 9 | PageType = Card; 10 | ApplicationArea = All; 11 | UsageCategory = Tasks; 12 | 13 | layout 14 | { 15 | area(content) 16 | { 17 | group(General) 18 | { 19 | Caption = 'General'; 20 | field(xDecimal; xDecimal) 21 | { 22 | Caption = 'Let the X be'; 23 | ToolTip = 'Specifies the X.'; 24 | ApplicationArea = All; 25 | DecimalPlaces = 2 : 5; 26 | } 27 | field(yDecimal; yDecimal) 28 | { 29 | Caption = 'Let the Y be'; 30 | ToolTip = 'Specifies the Y.'; 31 | ApplicationArea = All; 32 | DecimalPlaces = 2 : 5; 33 | } 34 | } 35 | } 36 | } 37 | actions 38 | { 39 | area(Processing) 40 | { 41 | action(MaxValue) 42 | { 43 | Caption = 'Get Max'; 44 | ToolTip = 'Gets max value of two.'; 45 | Image = Calculate; 46 | Promoted = true; 47 | PromotedCategory = Process; 48 | PromotedOnly = true; 49 | ApplicationArea = All; 50 | trigger OnAction() 51 | var 52 | Math: Codeunit Math; 53 | MaxIsMsg: Label 'Max is: %1'; 54 | begin 55 | Message(MaxIsMsg, Math.Max(xDecimal, yDecimal)); 56 | end; 57 | } 58 | action(MinValue) 59 | { 60 | Caption = 'Get Min'; 61 | ToolTip = 'Gets min value of two.'; 62 | Image = Calculate; 63 | Promoted = true; 64 | PromotedCategory = Process; 65 | PromotedOnly = true; 66 | ApplicationArea = All; 67 | trigger OnAction() 68 | var 69 | Math: Codeunit Math; 70 | MinIsMsg: Label 'Min is: %1'; 71 | begin 72 | Message(MinIsMsg, Math.Min(xDecimal, yDecimal)); 73 | end; 74 | } 75 | action(FloorValue) 76 | { 77 | Caption = 'Floor X'; 78 | ToolTip = 'Gets Floor value of X.'; 79 | Image = Calculate; 80 | Promoted = true; 81 | PromotedCategory = Process; 82 | PromotedOnly = true; 83 | ApplicationArea = All; 84 | trigger OnAction() 85 | var 86 | Math: Codeunit Math; 87 | FloorIsMsg: Label 'Floor is: %1'; 88 | begin 89 | Message(FloorIsMsg, Math.Floor(xDecimal)); 90 | end; 91 | } 92 | action(CeilingValue) 93 | { 94 | Caption = 'Ceiling X'; 95 | ToolTip = 'Gets Ceiling value of X.'; 96 | Image = Calculate; 97 | Promoted = true; 98 | PromotedCategory = Process; 99 | PromotedOnly = true; 100 | ApplicationArea = All; 101 | trigger OnAction() 102 | var 103 | Math: Codeunit Math; 104 | CeilingIsMsg: Label 'Ceiling is: %1'; 105 | begin 106 | Message(CeilingIsMsg, Math.Ceiling(xDecimal)); 107 | end; 108 | } 109 | action(TruncateValue) 110 | { 111 | Caption = 'Truncate X'; 112 | ToolTip = 'Gets Truncate value of X.'; 113 | Image = Calculate; 114 | Promoted = true; 115 | PromotedCategory = Process; 116 | PromotedOnly = true; 117 | ApplicationArea = All; 118 | trigger OnAction() 119 | var 120 | Math: Codeunit Math; 121 | TruncateIsMsg: Label 'Truncate is: %1'; 122 | begin 123 | Message(TruncateIsMsg, Math.Truncate(xDecimal)); 124 | end; 125 | } 126 | action(RemainderValue) 127 | { 128 | Caption = 'Remainder'; 129 | ToolTip = 'Gets Remainder value of X and Y.'; 130 | Image = Calculate; 131 | Promoted = true; 132 | PromotedCategory = Process; 133 | PromotedOnly = true; 134 | ApplicationArea = All; 135 | trigger OnAction() 136 | var 137 | Math: Codeunit Math; 138 | RemainderIsMsg: Label 'Remainder is: %1'; 139 | begin 140 | Message(RemainderIsMsg, Math.IEEERemainder(xDecimal, yDecimal)); 141 | end; 142 | } 143 | } 144 | } 145 | var 146 | xDecimal, yDecimal : Decimal; 147 | } 148 | -------------------------------------------------------------------------------- /al.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".AL-Go" 5 | } 6 | ], 7 | "settings": {} 8 | } 9 | --------------------------------------------------------------------------------