|
1 | | -# AL-Go Per Tenant Extension Template |
| 1 | +# Beyond PDF Signature - Custom Implementation Example |
2 | 2 |
|
3 | | -This template repository can be used for managing AppSource Apps for Business Central. |
| 3 | +This repository demonstrates how to implement custom PDF signature functionality in Microsoft Dynamics 365 Business Central using the Beyond PDF Signature extension. The example shows how to add PDF signing capabilities to the Sales Shipment Header table, but the pattern can be applied to any table in Business Central. |
4 | 4 |
|
5 | | -Please go to https://aka.ms/AL-Go to learn more. |
| 5 | +## Overview |
6 | 6 |
|
7 | | -## Contributing |
| 7 | +This implementation provides: |
| 8 | +- **PDF Generation**: Automatically generates PDFs from Business Central reports |
| 9 | +- **Digital Signature**: Allows users to digitally sign PDF documents |
| 10 | +- **Document Storage**: Saves signed PDFs directly to the record using Media fields |
| 11 | +- **User Interface**: Adds signing actions to existing pages with intuitive controls |
8 | 12 |
|
9 | | -Please read [this](https://github.com/microsoft/AL-Go/blob/main/Scenarios/Contribute.md) description on how to contribute to AL-Go for GitHub. |
| 13 | +## Architecture |
10 | 14 |
|
11 | | -We do not accept Pull Requests on the template repository directly. |
| 15 | +The solution consists of three main components: |
| 16 | + |
| 17 | +### 1. Table Extension ([`SalesShipmentHeader.TableExt.al`](app/src/SalesShipmentHeader.TableExt.al)) |
| 18 | +Extends the target table to store signed documents: |
| 19 | + |
| 20 | +```al |
| 21 | +tableextension 50251 "ABC Sales Shipment Header" extends "Sales Shipment Header" |
| 22 | +{ |
| 23 | + fields |
| 24 | + { |
| 25 | + field(50251; "ABC My Signed Document"; Media) |
| 26 | + { |
| 27 | + Caption = 'My Signed Document'; |
| 28 | + Description = 'Holds the signed version of the sales shipment document after signing.'; |
| 29 | + } |
| 30 | + } |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +### 2. Page Extension ([`PostedSalesShipment.PageExt.al`](app/src/PostedSalesShipment.PageExt.al)) |
| 35 | +Adds signing functionality to the user interface: |
| 36 | + |
| 37 | +- **Sign Document**: Action to initiate the signing process |
| 38 | +- **Show Signed Document**: Action to view previously signed documents |
| 39 | +- **Smart Enabling**: Actions are enabled/disabled based on document state |
| 40 | + |
| 41 | +### 3. Handler Codeunit ([`SignSalesShipment.Codeunit.al`](app/src/SignSalesShipment.Codeunit.al)) |
| 42 | +Implements the [`BYD PDF SIG IHandler`](app/src/SignSalesShipment.Codeunit.al:1) interface to handle PDF operations: |
| 43 | + |
| 44 | +- **LoadPDF()**: Generates PDF from Business Central reports |
| 45 | +- **SavePDF()**: Stores signed PDF back to the record |
| 46 | +- **Initialization**: Sets up the handler with the current record |
| 47 | + |
| 48 | +## How It Works |
| 49 | + |
| 50 | +### Signing Process Flow |
| 51 | + |
| 52 | +1. **User Initiates Signing**: User clicks "Sign Document with your Customer" action |
| 53 | +2. **PDF Generation**: System generates PDF using Business Central's report selection |
| 54 | +3. **Signature Interface**: [`BYD PDF SIG Signpad`](app/src/PostedSalesShipment.PageExt.al:23) page opens with the PDF |
| 55 | +4. **Digital Signing**: User and customer sign the document digitally |
| 56 | +5. **Document Storage**: Signed PDF is saved to the [`"ABC My Signed Document"`](app/src/SalesShipmentHeader.TableExt.al:5) field |
| 57 | +6. **UI Updates**: "Show Signed Document" action becomes available |
| 58 | + |
| 59 | +### Key Features |
| 60 | + |
| 61 | +- **Duplicate Prevention**: Warns users if document is already signed |
| 62 | +- **Report Integration**: Uses Business Central's standard report selection mechanism |
| 63 | +- **Media Storage**: Leverages native Media field type for efficient storage |
| 64 | +- **File Naming**: Generates consistent filenames for signed documents |
| 65 | + |
| 66 | +## Implementation Guide |
| 67 | + |
| 68 | +Follow these steps to implement PDF signature functionality for any table: |
| 69 | + |
| 70 | +### Step 1: Create Table Extension |
| 71 | + |
| 72 | +Create a table extension for your target table: |
| 73 | + |
| 74 | +```al |
| 75 | +tableextension [YourID] "[Your Prefix] [Table Name]" extends "[Target Table]" |
| 76 | +{ |
| 77 | + fields |
| 78 | + { |
| 79 | + field([YourFieldID]; "[Your Prefix] Signed Document"; Media) |
| 80 | + { |
| 81 | + Caption = 'Signed Document'; |
| 82 | + Description = 'Holds the signed version of the document after signing.'; |
| 83 | + } |
| 84 | + } |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +### Step 2: Create Handler Codeunit |
| 89 | + |
| 90 | +Implement the [`BYD PDF SIG IHandler`](app/src/SignSalesShipment.Codeunit.al:1) interface: |
| 91 | + |
| 92 | +```al |
| 93 | +codeunit [YourID] "[Your Prefix] Sign [Entity]" implements "BYD PDF SIG IHandler" |
| 94 | +{ |
| 95 | + var |
| 96 | + [YourRecord]: Record "[Your Table]"; |
| 97 | + TempBlob: Codeunit "Temp Blob"; |
| 98 | + IsInitialized: Boolean; |
| 99 | +
|
| 100 | + procedure LoadPDF(var PDFStream: InStream): Boolean |
| 101 | + begin |
| 102 | + // Generate PDF from your report |
| 103 | + // Use ReportSelection.GetPdfReportForCust() or Report.SaveAs() |
| 104 | + // Return true if successful |
| 105 | + end; |
| 106 | +
|
| 107 | + procedure SavePDF(var PDFStream: InStream): Boolean |
| 108 | + begin |
| 109 | + // Save signed PDF to your Media field |
| 110 | + [YourRecord]."[Your Prefix] Signed Document".ImportStream(PDFStream, FileName, 'application/pdf'); |
| 111 | + [YourRecord].Modify(); |
| 112 | + exit([YourRecord]."[Your Prefix] Signed Document".HasValue()); |
| 113 | + end; |
| 114 | +
|
| 115 | + procedure Set[YourRecord](Rec: Record "[Your Table]") |
| 116 | + begin |
| 117 | + [YourRecord] := Rec; |
| 118 | + IsInitialized := true; |
| 119 | + end; |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +### Step 3: Create Page Extension |
| 124 | + |
| 125 | +Add signing actions to your page: |
| 126 | + |
| 127 | +```al |
| 128 | +pageextension [YourID] "[Your Prefix] [Page Name]" extends "[Target Page]" |
| 129 | +{ |
| 130 | + actions |
| 131 | + { |
| 132 | + addafter([ExistingAction]) |
| 133 | + { |
| 134 | + action("[Your Prefix] Sign") |
| 135 | + { |
| 136 | + ApplicationArea = All; |
| 137 | + Caption = 'Sign Document'; |
| 138 | + Image = Signature; |
| 139 | + |
| 140 | + trigger OnAction() |
| 141 | + var |
| 142 | + [YourHandler]: Codeunit "[Your Prefix] Sign [Entity]"; |
| 143 | + PDFSignaturePage: Page "BYD PDF SIG Signpad"; |
| 144 | + begin |
| 145 | + // Initialize handler |
| 146 | + [YourHandler].Set[YourRecord](Rec); |
| 147 | + |
| 148 | + // Set handler in PDF signature page |
| 149 | + PDFSignaturePage.SetPDFHandler([YourHandler]); |
| 150 | + |
| 151 | + // Open signature page |
| 152 | + PDFSignaturePage.RunModal(); |
| 153 | + end; |
| 154 | + } |
| 155 | + |
| 156 | + action("[Your Prefix] ShowSigned") |
| 157 | + { |
| 158 | + ApplicationArea = All; |
| 159 | + Caption = 'Show Signed Document'; |
| 160 | + Image = DocumentEdit; |
| 161 | + Enabled = IsAlreadySigned; |
| 162 | + |
| 163 | + trigger OnAction() |
| 164 | + var |
| 165 | + TenantMedia: Record "Tenant Media"; |
| 166 | + InS: InStream; |
| 167 | + begin |
| 168 | + if TenantMedia.Get(Rec."[Your Prefix] Signed Document".MediaId()) then |
| 169 | + if TenantMedia.Content.HasValue then begin |
| 170 | + TenantMedia.CalcFields(Content); |
| 171 | + TenantMedia.Content.CreateInStream(InS); |
| 172 | + File.ViewFromStream(InS, 'SignedDocument.pdf'); |
| 173 | + end; |
| 174 | + end; |
| 175 | + } |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + trigger OnAfterGetCurrRecord() |
| 180 | + begin |
| 181 | + IsAlreadySigned := IsSignedAndSaved(); |
| 182 | + end; |
| 183 | +
|
| 184 | + local procedure IsSignedAndSaved(): Boolean |
| 185 | + var |
| 186 | + TenantMedia: Record "Tenant Media"; |
| 187 | + begin |
| 188 | + if not TenantMedia.Get(Rec."[Your Prefix] Signed Document".MediaId()) then |
| 189 | + exit(false); |
| 190 | + exit(TenantMedia.Content.HasValue()); |
| 191 | + end; |
| 192 | +
|
| 193 | + var |
| 194 | + IsAlreadySigned: Boolean; |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +## Prerequisites |
| 199 | + |
| 200 | +1. **Beyond PDF Signature Extension**: Install the Beyond PDF Signature extension in your Business Central environment from Appsource |
| 201 | +2. **Report Configuration**: Ensure your target table has associated reports configured in Report Selections |
| 202 | +3. **Permissions**: Users need appropriate permissions to modify the target table and access the signing functionality |
| 203 | + |
| 204 | +## Customization Options |
| 205 | + |
| 206 | +### PDF Source Customization |
| 207 | + |
| 208 | +Modify the [`LoadPDF()`](app/src/SignSalesShipment.Codeunit.al:14) method to: |
| 209 | +- Use different reports |
| 210 | +- Apply custom filters |
| 211 | +- Generate PDFs from multiple sources |
| 212 | +- Add custom headers/footers |
| 213 | + |
| 214 | +### Storage Customization |
| 215 | + |
| 216 | +Modify the [`SavePDF()`](app/src/SignSalesShipment.Codeunit.al:39) method to: |
| 217 | +- Save to Document Attachments instead of Media fields |
| 218 | +- Implement custom file naming conventions |
| 219 | +- Add metadata or tags |
| 220 | +- Integrate with external document management systems |
| 221 | + |
| 222 | +### UI Customization |
| 223 | + |
| 224 | +Modify the page extension to: |
| 225 | +- Add custom validation before signing |
| 226 | +- Implement approval workflows |
| 227 | +- Add audit trail functionality |
| 228 | +- Customize action placement and appearance |
| 229 | + |
| 230 | +## Best Practices |
| 231 | + |
| 232 | +1. **Error Handling**: Implement comprehensive error handling for PDF generation and storage |
| 233 | +2. **Performance**: Use temporary blobs for large PDF operations |
| 234 | +3. **Security**: Validate user permissions before allowing signing operations |
| 235 | +4. **Naming Conventions**: Use consistent prefixes to avoid conflicts |
| 236 | +5. **Testing**: Test with various document sizes and user scenarios |
| 237 | + |
| 238 | +## Troubleshooting |
| 239 | + |
| 240 | +### Common Issues |
| 241 | + |
| 242 | +- **PDF Generation Fails**: Check report selection configuration and permissions |
| 243 | +- **Signing Page Doesn't Open**: Verify Beyond PDF Signature extension is installed and configured |
| 244 | +- **Signed Document Not Saved**: Check table permissions and Media field configuration |
| 245 | + |
| 246 | +### Debug Tips |
| 247 | + |
| 248 | +- Use the debugger to step through the [`LoadPDF()`](app/src/SignSalesShipment.Codeunit.al:14) and [`SavePDF()`](app/src/SignSalesShipment.Codeunit.al:39) methods |
| 249 | +- Check the Event Log for any system errors |
| 250 | +- Verify the TempBlob has content before attempting to save |
| 251 | + |
| 252 | +## Support |
| 253 | + |
| 254 | +For issues related to the Beyond PDF Signature extension itself, contact the extension provider. For implementation questions, refer to the Microsoft Dynamics 365 Business Central documentation. |
| 255 | + |
| 256 | +## License |
| 257 | + |
| 258 | +This example implementation is provided as-is for educational purposes. Ensure compliance with your organization's licensing requirements when implementing in production environments. |
0 commit comments