-
Notifications
You must be signed in to change notification settings - Fork 366
External Storage - Document Attachments #4495
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
JesperSchulz
merged 79 commits into
microsoft:main
from
StefanSosic:dev/sso/externalStorageApp
Feb 12, 2026
Merged
Changes from 9 commits
Commits
Show all changes
79 commits
Select commit
Hold shift + click to select a range
c8e19e2
Add integration events for file scenario management and enhance UI ac…
StefanSosic 33071a9
Merge branch 'main' of https://github.com/StefanSosic/BCApps into dev…
StefanSosic b81f6eb
External Storage - Document Attachments
StefanSosic 4adc2b0
Interface + Improvements
StefanSosic d04ef8b
Fixes
StefanSosic 4978d52
Trailing space
StefanSosic 6357709
Internal
StefanSosic e5dbc09
Internal
StefanSosic 6ecd65f
app json fixes
StefanSosic c454a9a
DataClassification
StefanSosic b485579
Namespaces
StefanSosic a28fe49
InherentPermissions + InherentEntitlements
StefanSosic 26e1c3a
Update tooltip for sync direction field in DA External Storage Sync r…
StefanSosic 68ef801
Deafult Scenario
StefanSosic 8832186
Empty Line
StefanSosic ecd581a
Permission Refactor & Message
StefanSosic 4b57268
Add label for date formula in DA External Storage Sync report
StefanSosic 7eb7ebf
FIxes from PR comments
StefanSosic bf2f5b6
Fixes and Features
StefanSosic 509618d
Add disable check
StefanSosic 244043f
Remove prefix
StefanSosic 0db7610
Adjustments to deletion
StefanSosic ad8638e
Remove logger
StefanSosic e87efe6
Merge branch 'main' of https://github.com/StefanSosic/BCApps into dev…
StefanSosic f358ddb
Update readme.md
StefanSosic b6c89f6
Improve warning message
JesperSchulz 4f4c207
Fallback for table name if 3rd party App Uninstalled
StefanSosic 372d316
Merge branch 'dev/sso/externalStorageApp' of https://github.com/Stefa…
StefanSosic f12957b
Add migration report
StefanSosic 14164b2
File Scenario fix breaking change
StefanSosic 3e86854
Merge branch 'main' of https://github.com/StefanSosic/BCApps into dev…
StefanSosic 53648cf
Remove
StefanSosic 852773a
Fix
StefanSosic 3e45ce4
Remove upload
StefanSosic 3e9f8a5
Fix Copy
StefanSosic fca4ef1
Edge case
StefanSosic 4edda71
One Drive
StefanSosic 90f2a8a
Polishing
StefanSosic 36318ea
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz 3c06e87
Fixes & Polishing
StefanSosic c3fe884
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz 82a537f
Add setup page invocation automatically on insert scenario
StefanSosic 2687d6c
Improvements
StefanSosic e625564
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz 913abe5
Report optimize
StefanSosic cdd9fa2
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz fb1d1dd
Drill down
StefanSosic b34c455
Init true
StefanSosic c55ef6f
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz 94e326b
Fix
StefanSosic 07ed610
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz 1fd2540
Merge branch 'main' of https://github.com/StefanSosic/BCApps into dev…
StefanSosic b7ffcc4
Add tests for External Storage - Document Attachments functionality
StefanSosic 585f7f8
New Guid
StefanSosic 5eeff8d
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz bd39320
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz 711568d
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz 8c2d4f0
Updates
StefanSosic 8d65717
Update to 32
StefanSosic acd40e0
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz d9af01e
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz ebffc34
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz 2168b02
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz a0ce75c
Merge branch 'main' of https://github.com/StefanSosic/BCApps into dev…
StefanSosic 791c1f4
Merge branch 'dev/sso/externalStorageApp' of https://github.com/Stefa…
StefanSosic ebc2b5d
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz 11c33b1
Add telemetry tags.
JesperSchulz 32202ff
Update src/Apps/W1/External Storage - Document Attachments/app/src/Se…
StefanSosic ae20d03
Update src/System Application/App/External File Storage/src/Scenario/…
StefanSosic 929df45
Update src/System Application/App/External File Storage/src/Scenario/…
StefanSosic bf03c5c
Fixes to PR Comments
StefanSosic 87a03cc
Merge branch 'dev/sso/externalStorageApp' of https://github.com/Stefa…
StefanSosic 0ca6800
Update src/System Application/App/External File Storage/src/Scenario/…
StefanSosic af4a2cb
Updates
StefanSosic 717fe2c
All ApplicationArea
StefanSosic 5f375f8
Permissions
StefanSosic f477e66
Entitlements
StefanSosic 220dc0d
Merge branch 'main' of https://github.com/microsoft/BCApps into dev/s…
JesperSchulz 4f111fe
Merge branch 'dev/sso/externalStorageApp' of https://github.com/stefa…
JesperSchulz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Binary file added
BIN
+5.32 KB
src/Apps/W1/External Storage - Document Attachments/app/ExtensionLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 88 additions & 0 deletions
88
src/Apps/W1/External Storage - Document Attachments/app/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # Document Attachments External Storage for Microsoft Dynamics 365 Business Central | ||
|
|
||
| ## Overview | ||
|
|
||
| The External Storage extension provides seamless integration between Microsoft Dynamics 365 Business Central and external storage systems such as Azure Blob Storage, SharePoint, and File Shares. This extension automatically manages document attachments by storing them in external storage systems while maintaining full functionality within Business Central. | ||
|
|
||
| ## Key Features | ||
|
|
||
| ### **Automatic Upload** | ||
| - Automatically uploads new document attachments to configured external storage | ||
| - Supports multiple storage connectors via the File Account framework | ||
| - Generates unique file names to prevent collisions | ||
| - Maintains original file metadata and associations | ||
|
|
||
| ### **Flexible Deletion Policies** | ||
| - **Immediately**: Delete from internal storage right after external upload | ||
| - **1 Day**: Keep internally for 1 day before deletion | ||
| - **7 Days**: Keep internally for 7 days before deletion (default) | ||
| - **14 Days**: Keep internally for 14 days before deletion | ||
|
|
||
| ### **Bulk Operations** | ||
| - Synchronize multiple files between internal and external storage | ||
| - Bulk upload to external storage | ||
| - Bulk download from external storage | ||
| - Progress tracking with detailed reporting | ||
|
|
||
| ## Installation & Setup | ||
|
|
||
| ### Prerequisites | ||
| - Microsoft Dynamics 365 Business Central version 27.0 or later | ||
| - File Account module configured with external storage connector | ||
| - Appropriate permissions for file operations | ||
|
|
||
| ### Installation Steps | ||
|
|
||
| 1. **Configure File Account** | ||
| - Open **File Accounts** page | ||
| - Create a new File Account with your preferred connector: | ||
| - Azure Blob Storage | ||
| - SharePoint | ||
| - File Share | ||
| - Assign the account to **External Storage** scenario | ||
|
|
||
| 2. **Configure External Storage** | ||
| - Open **File Accounts** page | ||
| - Select assigned **External Storage** scenario | ||
| - Open **Additional Scenario Setup** | ||
| - Configure settings: | ||
| - **Auto Upload**: Enable automatic upload of new attachments | ||
| - **Delete After**: Set retention policy for internal storage | ||
|
|
||
| ### Configuration Options | ||
|
|
||
| #### Auto Upload Settings | ||
| - **Enabled**: New document attachments are automatically uploaded to external storage | ||
| - **Disabled**: Manual upload required via actions | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Automatic Mode | ||
| When Auto Upload is enabled: | ||
| 1. User attaches a document to any Business Central record | ||
| 2. System automatically uploads to external storage | ||
| 3. File remains accessible through standard attachment functionality | ||
| 4. Internal file is deleted based on configured retention policy | ||
|
|
||
| ### Manual Operations | ||
|
|
||
| #### Individual File Operations | ||
| From **Document Attachment - External** page: | ||
| - **Upload to External Storage**: Upload selected file | ||
| - **Download from External Storage**: Download file for viewing | ||
| - **Download to Internal Storage**: Restore file to internal storage | ||
| - **Delete from External Storage**: Remove file from external storage | ||
| - **Delete from Internal Storage**: Remove file from internal storage | ||
|
|
||
| #### Bulk Operations | ||
| From **External Storage Synchronize** report: | ||
| - **To External Storage**: Upload multiple files to external storage | ||
| - **From External Storage**: Download multiple files from external storage | ||
| - **Delete Expired Files**: Clean up files based on retention policy | ||
|
|
||
| ### File Access | ||
| - Files uploaded to external storage remain fully accessible through standard Business Central functionality | ||
| - Document preview, download, and management work seamlessly | ||
| - No change to end-user experience | ||
|
|
||
| **© 2025 Microsoft Corporation. All rights reserved.** |
35 changes: 35 additions & 0 deletions
35
src/Apps/W1/External Storage - Document Attachments/app/app.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| { | ||
| "id": "5f2e93a0-6083-4718-b05a-7ac89be5644d", | ||
| "name": "External Storage - Document Attachments", | ||
| "publisher": "Microsoft", | ||
| "version": "27.0.0.0", | ||
| "brief": "External Storage processor for Business Central document attachments", | ||
| "description": "Processes document attachments from Business Central and stores them in configured External Storage connectors (Azure Blob, File Share, SharePoint)", | ||
| "privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009", | ||
| "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120", | ||
| "help": "https://go.microsoft.com/fwlink/?linkid=2134520", | ||
| "url": "https://go.microsoft.com/fwlink/?linkid=724011", | ||
| "logo": "ExtensionLogo.png", | ||
| "application": "27.0.0.0", | ||
| "platform": "27.0.0.0", | ||
| "internalsVisibleTo": [ | ||
| ], | ||
| "dependencies": [], | ||
| "screenshots": [], | ||
| "idRanges": [ | ||
| { | ||
| "from": 8750, | ||
| "to": 8770 | ||
| } | ||
| ], | ||
| "resourceExposurePolicy": { | ||
| "allowDebugging": true, | ||
| "allowDownloadingSource": true, | ||
| "includeSourceInSymbolFile": true | ||
| }, | ||
| "features": [ | ||
| "TranslationFile" | ||
| ], | ||
| "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520", | ||
| "target": "Cloud" | ||
| } | ||
174 changes: 174 additions & 0 deletions
174
...rnal Storage - Document Attachments/app/src/AutomaticSync/DAExternalStorageSync.Report.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
|
|
||
| namespace Microsoft.ExternalStorage.DocumentAttachments; | ||
|
|
||
| using Microsoft.Foundation.Attachment; | ||
|
|
||
| /// <summary> | ||
| /// Report for synchronizing document attachments between internal and external storage. | ||
| /// Supports bulk upload, download, and cleanup operations. | ||
| /// </summary> | ||
| report 8752 "DA External Storage Sync" | ||
| { | ||
| Caption = 'External Storage Synchronization'; | ||
| ProcessingOnly = true; | ||
| UseRequestPage = true; | ||
| Extensible = false; | ||
| UsageCategory = None; | ||
| Permissions = tabledata "DA External Storage Setup" = r, | ||
| tabledata "Document Attachment" = r; | ||
|
|
||
| dataset | ||
| { | ||
| dataitem(DocumentAttachment; "Document Attachment") | ||
| { | ||
| trigger OnPreDataItem() | ||
| begin | ||
| SetFilters(); | ||
| TotalCount := Count(); | ||
|
|
||
| if TotalCount = 0 then begin | ||
| if GuiAllowed() then | ||
| Message(NoRecordsMsg); | ||
| CurrReport.Break(); | ||
| end; | ||
|
|
||
| if MaxRecordsToProcess > 0 then | ||
| if TotalCount > MaxRecordsToProcess then | ||
| TotalCount := MaxRecordsToProcess; | ||
|
|
||
| ProcessedCount := 0; | ||
| FailedCount := 0; | ||
| DeleteCount := 0; | ||
| DeleteFailedCount := 0; | ||
|
|
||
| if GuiAllowed() then | ||
| Dialog.Open(ProcessingMsg, TotalCount); | ||
| end; | ||
|
|
||
| trigger OnAfterGetRecord() | ||
| begin | ||
| ProcessedCount += 1; | ||
|
|
||
| if GuiAllowed() then | ||
| Dialog.Update(1, ProcessedCount); | ||
|
|
||
| case SyncDirection of | ||
| SyncDirection::"To External Storage": | ||
|
|
||
| if not ExternalStorageProcessor.UploadToExternalStorage(DocumentAttachment) then | ||
| FailedCount += 1; | ||
| SyncDirection::"From External Storage": | ||
|
|
||
|
StefanSosic marked this conversation as resolved.
Outdated
|
||
| if not ExternalStorageProcessor.DownloadFromExternalStorage(DocumentAttachment) then | ||
| FailedCount += 1; | ||
| end; | ||
| if DeleteExpiredFiles then | ||
| if CalcDate('<+' + GetDateFormulaFromExternalStorageSetup() + '>', DocumentAttachment."External Upload Date".Date()) >= Today() then | ||
|
StefanSosic marked this conversation as resolved.
Outdated
|
||
| if ExternalStorageProcessor.DeleteFromInternalStorage(DocumentAttachment) then | ||
| DeleteCount += 1 | ||
| else | ||
| DeleteFailedCount += 1; | ||
|
StefanSosic marked this conversation as resolved.
Outdated
|
||
|
|
||
| if (MaxRecordsToProcess > 0) and (ProcessedCount >= MaxRecordsToProcess) then | ||
| CurrReport.Break(); | ||
| end; | ||
|
|
||
| trigger OnPostDataItem() | ||
| begin | ||
| if GuiAllowed() then begin | ||
| if TotalCount <> 0 then | ||
| Dialog.Close(); | ||
| if DeleteExpiredFiles then | ||
| Message(DeletedExpiredFilesMsg, ProcessedCount - FailedCount, FailedCount, DeleteCount) | ||
| else | ||
| Message(ProcessedMsg, ProcessedCount - FailedCount, FailedCount); | ||
| end; | ||
| end; | ||
| } | ||
| } | ||
|
|
||
| requestpage | ||
| { | ||
| SaveValues = true; | ||
|
|
||
| layout | ||
| { | ||
| area(Content) | ||
| { | ||
| group(General) | ||
| { | ||
| Caption = 'General'; | ||
| field(SyncDirectionField; SyncDirection) | ||
| { | ||
| ApplicationArea = Basic, Suite; | ||
| Caption = 'Sync Direction'; | ||
| OptionCaption = 'To External Storage,From External Storage'; | ||
| ToolTip = 'Specifies whether to sync to external storage, from external storage, or delete expired files.'; | ||
|
StefanSosic marked this conversation as resolved.
Outdated
|
||
| } | ||
| field(DeleteExpiredFilesField; DeleteExpiredFiles) | ||
| { | ||
| ApplicationArea = Basic, Suite; | ||
| Caption = 'Delete Expired Files'; | ||
| ToolTip = 'Specifies whether to delete expired files from internal storage.'; | ||
| } | ||
| field(MaxRecordsToProcessField; MaxRecordsToProcess) | ||
| { | ||
| ApplicationArea = Basic, Suite; | ||
| Caption = 'Maximum Records to Process'; | ||
| ToolTip = 'Specifies the maximum number of records to process in one run. Leave 0 for unlimited.'; | ||
| MinValue = 0; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| var | ||
| ExternalStorageProcessor: Codeunit "DA External Storage Processor"; | ||
| DeleteExpiredFiles: Boolean; | ||
| Dialog: Dialog; | ||
| DeleteCount, DeleteFailedCount : Integer; | ||
| FailedCount: Integer; | ||
| MaxRecordsToProcess: Integer; | ||
| ProcessedCount: Integer; | ||
| TotalCount: Integer; | ||
| DeletedExpiredFilesMsg: Label 'Processed %1 attachments successfully. %2 failed.//Deleted %3 expired files.', Comment = '%1 - Number of Processed Attachments, %2 - Number of Failed Attachments, %3 - Number of Deleted Expired Files'; | ||
| NoRecordsMsg: Label 'No records found to process.'; | ||
| ProcessedMsg: Label 'Processed %1 attachments successfully. %2 failed.', Comment = '%1 - Number of Processed Attachments, %2 - Number of Failed Attachments'; | ||
| ProcessingMsg: Label 'Processing #1###### attachments...', Comment = '%1 - Total Number of Attachments'; | ||
| SyncDirection: Option "To External Storage","From External Storage"; | ||
|
|
||
| local procedure SetFilters() | ||
| begin | ||
| case SyncDirection of | ||
| SyncDirection::"To External Storage": | ||
| begin | ||
| DocumentAttachment.SetRange("Uploaded Externally", false); | ||
| if DocumentAttachment.FindSet() then begin | ||
| repeat | ||
| if not DocumentAttachment."Document Reference ID".HasValue() then | ||
| DocumentAttachment.Mark(false) | ||
|
StefanSosic marked this conversation as resolved.
Outdated
|
||
| else | ||
| DocumentAttachment.Mark(true); | ||
| until DocumentAttachment.Next() = 0; | ||
| DocumentAttachment.MarkedOnly(true); | ||
| end; | ||
| end; | ||
| SyncDirection::"From External Storage": | ||
|
|
||
|
StefanSosic marked this conversation as resolved.
Outdated
|
||
| DocumentAttachment.SetRange("Uploaded Externally", true); | ||
| end; | ||
| end; | ||
|
|
||
| local procedure GetDateFormulaFromExternalStorageSetup(): Text | ||
| var | ||
| ExternalStorageSetup: Record "DA External Storage Setup"; | ||
| begin | ||
| ExternalStorageSetup.Get(); | ||
| exit(Format(ExternalStorageSetup."Delete After".AsInteger()) + 'D'); | ||
| end; | ||
| } | ||
31 changes: 31 additions & 0 deletions
31
src/Apps/W1/External Storage - Document Attachments/app/src/DAExtStorageDeleteAfter.Enum.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
|
|
||
| namespace Microsoft.ExternalStorage.DocumentAttachments; | ||
|
|
||
| /// <summary> | ||
| /// Defines when attachments should be deleted from internal storage after upload to external storage. | ||
| /// </summary> | ||
| enum 8750 "DA Ext. Storage - Delete After" | ||
|
StefanSosic marked this conversation as resolved.
Outdated
|
||
| { | ||
| Extensible = false; | ||
|
|
||
| value(0; "Immediately") | ||
| { | ||
| Caption = 'Immediately'; | ||
| } | ||
| value(1; "1 Day") | ||
| { | ||
| Caption = '1 Day'; | ||
| } | ||
| value(7; "7 Days") | ||
| { | ||
| Caption = '7 Days'; | ||
| } | ||
| value(14; "14 Days") | ||
| { | ||
| Caption = '14 Days'; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.