<< Click to Display Table of Contents >> Navigation: Zetadocs SDK Guide > Creating a Per Tenant Extension > Barcode Generator |
Zetadocs AutoLink allows you to archive documents from the Zetadocs Document Queue against records in Business Central. The logic is usually reliant on use of barcodes. e.g. As soon as a supplier invoice has been entered onto Business Central, a barcoded label is produced by a small desktop printer for sticking onto the invoice. It can then be scanned and automatically linked to the relevant transaction in Business Central. This topic describes how to produce the barcode label.
There are three main steps to this process:
Note: That these instructions are not dependent on Zetadocs being installed.
Create Barcode Table as below, this table stores the Barcode values and the image produced from a Barcode Web API, and is used by the report later:
table 50690 Barcode
{
fields
{
field(1; PrimaryKey; Guid)
{
Caption = 'Primary Key';
DataClassification = SystemMetadata;
}
field(2; Value; Text[250])
{
Caption = 'Value';
DataClassification = CustomerContent;
}
field(3; Type; Option)
{
Caption = 'Type';
DataClassification = SystemMetadata;
//If you want to use the Barcodes4me API then uncomment the code below.
// Barcodes4me WebAPI
//OptionMembers = " ",c39,c128a,c128b,c128c,i2of5,qr;
//OptionCaption = 'Select a Type,Code 39,Code 128a,Code 128b,Code 128c,2 of 5 Interleaved,QR Code';
//If you want to use the Barcodes4me API then comment out the code below.
// Tec-IT WebAPI
OptionMembers = " ",Code11,QRCode,Code128,Code25IL,Code39,Code39FullASCII,Code93,EANUCC128,MSI,TelepenAlpha,EAN8,EAN13,UPCA,UPCE;
OptionCaption = 'Select a Type,Code 11,QR Code,Code 128,2 of 5 Interleaved,Code 39,Code 39 Full ASCII,Code 93,EAN 128,MSI,Telepen Alpha,EAN 8,EAN 13,UPCA,UPCE';
}
field(4; Width; Option)
{
Caption = 'Width';
DataClassification = SystemMetadata;
//If you want to use the Barcodes4me API then uncomment the code below.
// Barcodes4me WebAPI
//InitValue = 250;
//If you want to use the Barcodes4me API then comment out the code below.
// Tec-IT WebAPI
OptionCaption = 'Narrow, Normal, Wide';
OptionMembers = "1","2","3";
}
field(5; Height; Integer)
{
Caption = 'Height';
DataClassification = SystemMetadata;
InitValue = 100;
}
field(6; IncludeText; Boolean)
{
Caption = 'Include Text';
DataClassification = SystemMetadata;
}
field(7; Border; Boolean)
{
Caption = 'Border';
DataClassification = SystemMetadata;
}
field(8; ReverseColors; Boolean)
{
Caption = 'Reverse Colors';
DataClassification = SystemMetadata;
}
field(9; ECCLevel; Option)
{
Caption = 'ECC Level';
DataClassification = SystemMetadata;
//If you want to use the Barcodes4me API then uncomment the code below.
// Barcodes4me WebAPI
//OptionMembers = "0","1","2","3";
//If you want to use the Barcodes4me API then comment out the code below.
// Tec-IT WebApi
OptionMembers = "L","M","Q","H";
OptionCaption = 'Low (L),Medium-Low (M),Medium-High (Q),High (H)';
}
field(10; Size; Option)
{
DataClassification = SystemMetadata;
//If you want to use the Barcodes4me API then uncomment the code below.
// Barcodes4me WebAPI
// Caption = 'Size';
//OptionMembers = "1","2","3","4","5","6","7","8","9","10";
//OptionCaption = '21x21,42x42,63x63,84x84,105x105,126x126,147x147,168x168,189x189,210x210';
//If you want to use the Barcodes4me API then comment out the code below.
// Tec-IT WebAPI
Caption = 'DPI';
OptionMembers = "72","96","128";
OptionCaption = '72,96,128';
}
field(11; PictureType; Option)
{
Caption = 'Picture Type';
DataClassification = SystemMetadata;
OptionMembers = png,gif,jpg;
OptionCaption = 'png,gif,jpg';
}
field(12; Picture; Media)
{
Caption = 'Picture';
DataClassification = SystemMetadata;
}
}
keys
{
key(PK; PrimaryKey)
{
Clustered = false;
}
}
trigger OnInsert();
begin
PrimaryKey := CreateGuid;
GenerateBarcode();
end;
trigger OnModify();
begin
GenerateBarcode();
end;
procedure GenerateBarcode()
var
GenerateBarcodeCode: Codeunit GenerateBarcode;
begin
GenerateBarcodeCode.GenerateBarcode(Rec);
end;
}
Create a second table to store the Web Service parameters:
table 50691 RESTWebServiceArguments
{
fields
{
field(1; PrimaryKey; Integer) { DataClassification = SystemMetadata; }
field(2; RestMethod; Option)
{
DataClassification = SystemMetadata;
OptionMembers = get,post,delete,patch,put;
}
field(3; URL; Text[250]) { DataClassification = SystemMetadata; }
field(4; Accept; Text[30]) { DataClassification = SystemMetadata; }
field(5; ETag; Text[250]) { DataClassification = SystemMetadata; }
field(6; UserName; text[50]) { DataClassification = SystemMetadata; }
field(7; Password; text[50]) { DataClassification = SystemMetadata; }
field(100; Blob; Blob) { DataClassification = SystemMetadata; }
}
keys
{
key(PK; PrimaryKey)
{
Clustered = true;
}
}
var
RequestContent: HttpContent;
RequestContentSet: Boolean;
ResponseHeaders: HttpHeaders;
procedure SetRequestContent(var value: HttpContent)
begin
RequestContent := value;
RequestContentSet := true;
end;
procedure HasRequestContent(): Boolean
begin
exit(RequestContentSet);
end;
procedure GetRequestContent(var value: HttpContent)
begin
value := RequestContent;
end;
procedure SetResponseContent(var value: HttpContent)
var
InStr: InStream;
OutStr: OutStream;
begin
Blob.CreateInStream(InStr);
value.ReadAs(InStr);
Blob.CreateOutStream(OutStr);
CopyStream(OutStr, InStr);
end;
procedure HasResponseContent(): Boolean
begin
exit(Blob.HasValue);
end;
procedure GetResponseContent(var value: HttpContent)
var
InStr: InStream;
begin
Blob.CreateInStream(InStr);
value.Clear();
value.WriteFrom(InStr);
end;
procedure GetResponseContentAsText() ReturnValue: text
var
InStr: InStream;
Line: text;
begin
if not HasResponseContent then
exit;
Blob.CreateInStream(InStr);
InStr.ReadText(ReturnValue);
while not InStr.EOS do begin
InStr.ReadText(Line);
ReturnValue += Line;
end;
end;
procedure SetResponseHeaders(var value: HttpHeaders)
begin
ResponseHeaders := value;
end;
procedure GetResponseHeaders(var value: HttpHeaders)
begin
value := ResponseHeaders;
end;
}
Next create Codeunits used to call the Web Service, firstly a method to handle the JSON:
codeunit 50691 JSONMethods
{
procedure GetJsonValueAsText(var JSonObject:JsonObject; Property:Text) Value:text
var
JsonValue:JsonValue;
begin
if not GetJsonValue(JSonObject,Property,JsonValue) then
exit;
Value := JsonValue.AsText;
end;
procedure GetJsonValue(var JSonObject:JsonObject; Property:Text; var JsonValue:JsonValue) :Boolean
var
JsonToken:JsonToken;
begin
if not JSonObject.Get(Property,JsonToken) then
exit;
JsonValue := JsonToken.AsValue();
exit(true);
end;
}
The next codeunit to create is for calling REST web services:
codeunit 50692 RESTWebServiceCode
{
procedure CallRESTWebService(var Parameters: Record RESTWebServiceArguments): Boolean
var
Base64Convert: Codeunit "Base64 Convert";
TempBlob: Codeunit "Temp Blob";
Client: HttpClient;
Headers: HttpHeaders;
RequestMessage: HttpRequestMessage;
ResponseMessage: HttpResponseMessage;
Content: HttpContent;
AuthText: Text;
convertOutputText: Text;
blobInStream: InStream;
blobOutStream: OutStream;
begin
RequestMessage.Method := Format(Parameters.RestMethod);
RequestMessage.SetRequestUri(Parameters.URL);
RequestMessage.GetHeaders(Headers);
if Parameters.Accept <> '' then
Headers.Add('Accept', Parameters.Accept);
if Parameters.UserName <> '' then begin
AuthText := StrSubstNo('%1:%2', Parameters.UserName, Parameters.Password);
TempBlob.CreateOutStream(blobOutStream, TextEncoding::Windows);
blobOutStream.WriteText(AuthText);
TempBlob.CreateInStream(blobInstream, TextEncoding::Windows);
convertOutputText := Base64Convert.ToBase64(blobInstream);
Headers.Add('Authorization', StrSubstNo('Basic %1', convertOutputText));
end;
if Parameters.ETag <> '' then
Headers.Add('If-Match', Parameters.ETag);
if Parameters.HasRequestContent then begin
Parameters.GetRequestContent(Content);
RequestMessage.Content := Content;
end;
Client.Send(RequestMessage, ResponseMessage);
Headers := ResponseMessage.Headers;
Parameters.SetResponseHeaders(Headers);
Content := ResponseMessage.Content;
Parameters.SetResponseContent(Content);
EXIT(ResponseMessage.IsSuccessStatusCode);
end;
}
The next codeunit to create is for calling the specific Web API that will generate the barcode image, the code contains calls to two different web API’s, uncomment the Barcodes4me code if you would prefer to use that API instead of the Tec-IT.
Note: That you will need to un/comment code in the Barcode Table as well.
codeunit 50690 GenerateBarcode
{
procedure GenerateBarcode(var Barcode: Record Barcode): Boolean
begin
exit(DoGenerateBarcode(Barcode));
end;
local procedure DoGenerateBarcode(var Barcode: Record Barcode): Boolean
var
Arguments: Record RESTWebServiceArguments temporary;
begin
InitArguments(Arguments, Barcode);
if not CallWebService(Arguments) then begin
exit(false);
end;
SaveResult(Arguments, Barcode);
exit(true);
end;
local procedure InitArguments(var Arguments: Record RESTWebServiceArguments temporary; Barcode: Record Barcode)
var
TypeHelper: Codeunit "Type Helper";
BaseURL: Text;
begin
//Depending on the WebAPI you want ot call you should uncomment the code below, currently the Tec-It webapi
//is being used, if you want to use the Barcodes4me Web API cal lthen uncomment the code below and comment
//out the code between the Tec-It comments.
// Barcodes4me WebAPI - Start
// BaseURL := 'http://barcodes4.me';
// if Barcode.Type = Barcode.Type::qr then
// Arguments.URL := StrSubstNo('%1/barcode/qr/qrcode.%3?value=%2?size=%4?ecclevel=%5',
// BaseURL,
// TypeHelper.UrlEncode(Barcode.Value),
// GetOptionStringValue(Barcode.PictureType, Barcode.FieldNo(PictureType)),
// GetOptionStringValue(Barcode.Size, Barcode.FieldNo(Size)),
// GetOptionStringValue(Barcode.ECCLevel, Barcode.FieldNo(ECCLevel)))
// else
// Arguments.URL := StrSubstNo('%1/barcode/%2/%3.%4?IsTextDrawn=%5&IsBorderDrawn=%6&IsReverseColor=%7',
// BaseURL,
// GetOptionStringValue(Barcode.Type, Barcode.FieldNo(Type)),
// TypeHelper.UrlEncode(Barcode.Value),
// GetOptionStringValue(Barcode.PictureType, Barcode.FieldNo(PictureType)),
// Format(Barcode.IncludeText, 0, 2),
// Format(Barcode.Border, 0, 2),
// Format(Barcode.ReverseColors, 0, 2));
//Barcodes4me WebAPI-End
// Tec-It WebAPI - Start
BaseURL := 'https://barcode.tec-it.com/barcode.ashx?data=';
if (Barcode.Type = Barcode.Type::QRCode) then
Arguments.URL := StrSubstNo('%1%2&code=%3&unit=Px&dpi=%4&imagetype=%5&modulewidth=%6&eclevel=&7',
BaseURL,
TypeHelper.UrlEncode(Barcode.Value),
GetOptionStringValue(Barcode.Type, Barcode.FieldNo(Type)),
GetOptionStringValue(Barcode.Size, Barcode.FieldNo(Size)),
GetOptionStringValue(Barcode.PictureType, Barcode.FieldNo(PictureType)),
GetOptionStringValue(Barcode.Width, Barcode.FieldNo(Width)),
GetOptionStringValue(Barcode.ECCLevel, Barcode.FieldNo(ECCLevel)))
else
Arguments.URL := StrSubstNo('%1%2&code=%3&unit=Px&dpi=%4&imagetype=%5&modulewidth=%6',
BaseURL,
TypeHelper.UrlEncode(Barcode.Value),
GetOptionStringValue(Barcode.Type, Barcode.FieldNo(Type)),
GetOptionStringValue(Barcode.Size, Barcode.FieldNo(Size)),
GetOptionStringValue(Barcode.PictureType, Barcode.FieldNo(PictureType)),
GetOptionStringValue(Barcode.Width, Barcode.FieldNo(Width)));
// Tec-It WebAPI - End
Arguments.RestMethod := Arguments.RestMethod::get;
end;
local procedure CallWebService(var Arguments: Record RESTWebServiceArguments temporary) Success: Boolean
var
RESTWebService: codeunit RESTWebServiceCode;
begin
Success := RESTWebService.CallRESTWebService(Arguments);
end;
local procedure SaveResult(var Arguments: Record RESTWebServiceArguments temporary; var Barcode: Record Barcode)
var
ResponseContent: HttpContent;
InStr: InStream;
TempBlob: Codeunit "Temp Blob";
begin
Arguments.GetResponseContent(ResponseContent);
TempBlob.CreateInStream(InStr);
ResponseContent.ReadAs(InStr);
Clear(Barcode.Picture);
Barcode.Picture.ImportStream(InStr, Barcode.Value);
Barcode.Modify();
end;
local procedure GetOptionStringValue(Value: Integer; fieldno: Integer): Text
var
FieldRec: Record Field;
begin
FieldRec.Get(Database::Barcode, fieldno);
exit(SelectStr(Value + 1, FieldRec.OptionString));
end;
}
We need to create a Report to print the generated barcode, this involves two files, a new Report, and a new Custom Report Layout. More information about creating reports can be found on the Microsoft site here.
The code below is for the report that prints out the barcode, if you want more fields than just the barcode then you can add further columns under the dataset/dataitem section:
report 50693 ItemListBarcode
{
UsageCategory = Administration;
ApplicationArea = All;
DefaultLayout = RDLC;
RDLCLayout = 'ItemListBarcode.rdl';
dataset
{
dataitem(DataItemName; Barcode)
{
column(Picture; Picture)
{
}
}
}
requestpage
{
layout
{
area(Content)
{
}
}
actions
{
area(processing)
{
action(ActionName)
{
ApplicationArea = All;
}
}
}
}
}
Next, create the Custom Report Layout that the report uses, create the following file:
Note: More information about custom report layouts from Microsoft can be found here.
This file should be named to match the RDLCLayout value in the previous codeunit ‘ItemListBarcode.rdl’
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
<AutoRefresh>0</AutoRefresh>
<DataSources>
<DataSource Name="DataSource">
<ConnectionProperties>
<DataProvider>SQL</DataProvider>
<ConnectString />
</ConnectionProperties>
<rd:SecurityType>None</rd:SecurityType>
<rd:DataSourceID>af7a58e2-0a8d-4063-95f6-056a69099326</rd:DataSourceID>
</DataSource>
</DataSources>
<ReportSections>
<ReportSection>
<Body>
<ReportItems>
<Tablix Name="Tablix1">
<TablixBody>
<TablixColumns>
<TablixColumn>
<Width>3.02778in</Width>
</TablixColumn>
</TablixColumns>
<TablixRows>
<TablixRow>
<Height>0.77084in</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Image Name="Image1">
<Source>Database</Source>
<Value>=Fields!Picture.Value</Value>
<MIMEType>image/png</MIMEType>
<Sizing>FitProportional</Sizing>
<Style>
<Border>
<Style>None</Style>
</Border>
</Style>
</Image>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
</TablixRows>
</TablixBody>
<TablixColumnHierarchy>
<TablixMembers>
<TablixMember />
</TablixMembers>
</TablixColumnHierarchy>
<TablixRowHierarchy>
<TablixMembers>
<TablixMember>
<Group Name="Details" />
</TablixMember>
</TablixMembers>
</TablixRowHierarchy>
<DataSetName>DataSet_Result</DataSetName>
<Top>0.125in</Top>
<Left>0.20833in</Left>
<Height>0.77084in</Height>
<Width>3.02778in</Width>
<Style>
<Border>
<Style>None</Style>
</Border>
</Style>
</Tablix>
</ReportItems>
<Height>0.98959in</Height>
<Style />
</Body>
<Width>7.03125in</Width>
<Page>
<Style />
</Page>
</ReportSection>
</ReportSections>
<Code>Public Function BlankZero(ByVal Value As Decimal)
if Value = 0 then
Return ""
end if
Return Value
End Function
Public Function BlankPos(ByVal Value As Decimal)
if Value > 0 then
Return ""
end if
Return Value
End Function
Public Function BlankZeroAndPos(ByVal Value As Decimal)
if Value >= 0 then
Return ""
end if
Return Value
End Function
Public Function BlankNeg(ByVal Value As Decimal)
if Value < 0 then
Return ""
end if
Return Value
End Function
Public Function BlankNegAndZero(ByVal Value As Decimal)
if Value <= 0 then
Return ""
end if
Return Value
End Function
</Code>
<Language>=User!Language</Language>
<ConsumeContainerWhitespace>true</ConsumeContainerWhitespace>
<rd:ReportUnitType>Inch</rd:ReportUnitType>
<rd:ReportID>0eeb6585-38ae-40f1-885b-8d50088d51b4</rd:ReportID>
<DataSets>
<DataSet Name="DataSet_Result">
<Fields>
<Field Name="Picture">
<DataField>Picture</DataField>
</Field>
</Fields>
<Query>
<DataSourceName>DataSource</DataSourceName>
<CommandText />
</Query>
</DataSet>
</DataSets>
</Report>
Finally create the permission sets needed to access the two new tables create, this should be in your root folder:
<PermissionSets>
<PermissionSet RoleID="BarcodeRoleId" RoleName="BarcodeRoleId">
<Permission>
<ObjectType>TableData</ObjectType>
<ObjectID>50690</ObjectID>
<ReadPermission>Yes</ReadPermission>
<InsertPermission>Yes</InsertPermission>
<ModifyPermission>Yes</ModifyPermission>
<DeletePermission>Yes</DeletePermission>
<ExecutePermission>No</ExecutePermission>
<SecurityFilter />
</Permission>
<Permission>
<ObjectType>TableData</ObjectType>
<ObjectID>50691</ObjectID>
<ReadPermission>Yes</ReadPermission>
<InsertPermission>Yes</InsertPermission>
<ModifyPermission>Yes</ModifyPermission>
<DeletePermission>Yes</DeletePermission>
<ExecutePermission>No</ExecutePermission>
<SecurityFilter />
</Permission>
</PermissionSet>
</PermissionSets>
The final step is to create the Page Extension to call the Barcode Generator, the example below is to add a button to the Zetadocs group under the Actions menu, it is possible to make this modification to any page.
The important code happens in the OnAction trigger, here we are setting the parameters for the Barcode creation, if you want to generate a different Barcode type, say QR Code you can select a different value, so for example:
Barcode.Type := Barcode.Type::QRCode;
Note: Be careful when setting the Barcode.Value that this matches the format setup in the Autolinking Per Tenant Extension, otherwise the record will not be matched.
pageextension 50690 "Custom Purchase Order" extends "Purchase Order"
{
actions
{
addfirst("Zetadocs")
{
action("Generate Barcode")
{
Caption = 'Generate Barcode';
Image = BarCode;
ApplicationArea = All;
ToolTip = 'Generate a Barcode';
Visible = true;
trigger OnAction()
var
Barcode: Record Barcode;
GenerateBarcode: Codeunit GenerateBarcode;
begin
//Create new Barcode record to store values.
Barcode.Init();
Barcode.PrimaryKey := CreateGuid();
//Setting the DPI to 96
Barcode.Size := Barcode.Size::"96";
//Setting to Code 3 of 9 format
//Change this value if you want a different barcode type.
Barcode.Type := Barcode.Type::Code39;
//Setting the value to encode to the Purchase Invoice Number
Barcode.Value := StrSubstNo('ZD-PO%1', Rec."No.");
//Output format of image is png, can be jpg or gif.
Barcode.PictureType := Barcode.PictureType::png;
//True/false flag if you want text under the barcode
Barcode.IncludeText := true;
Barcode.Border := false;
Barcode.ReverseColors := false;
Barcode.Insert();
Commit();
//Generate Barcode Image
if (GenerateBarcode.GenerateBarcode(Barcode)) then begin
Commit();
//Run report to print Barcode
Barcode.SetFilter(PrimaryKey, Barcode.PrimaryKey);
Report.RunModal(50693, false, true, Barcode);
//Delete Record as no longer needed
Barcode.Delete();
Commit();
end else begin
//Optional: Error message for Barcode4me WebAPI calls
Message(StrSubstNo('%1 is an invalid barcode value for barcode type %2.', Barcode.Value, Barcode.Type));
end;
end;
}
}
}
}