SAP Business One
This guide walks you through the steps required to connect Docflo.ai to your SAP Business One system, enabling seamless document integration and automated data exchange.
- SAP Business One system with DI API access
- Visual Studio or development environment for .NET
- Docflo.ai account with API access
- Administrative access to Business One server
- SSL certificate management capabilities
π Integration Stepsβ
Follow these steps to establish a secure connection between Docflo.ai and your SAP Business One system:
Step 1: Import SSL Certificate Chainβ
- Download our SSL certificate chain (link)
- Import the certificate to the Windows Certificate Store on your Business One server
- Add the certificate to Trusted Root Certification Authorities
- Ensure the certificate is properly validated for HTTPS connections
Step 2: Configure Business One Service Layer (Optional)β
If using Service Layer for REST API access:
- Enable Service Layer in your Business One installation
- Configure HTTPS with proper SSL certificates
- Set up authentication (Basic or Windows Authentication)
- Test the Service Layer endpoint connectivity
Step 3: Generate API Credentialsβ
- Go to the "Integrations" section in your Docflo.ai platform
- Create an API key for SAP Business One integration
- Copy the API key and store it securely
- Copy the tenant ID as well - you'll need both for the integration
Step 4: Create Business One Integration Serviceβ
Create a Windows Service or scheduled application using the DI API. Here's a sample C# implementation:
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SAPbobsCOM;
namespace DocfloBusinessOneIntegration
{
public class DocfloIntegrationService
{
private Company _company;
private readonly string _docfloApiUrl;
private readonly string _tenantId;
private readonly string _apiKey;
private readonly HttpClient _httpClient;
public DocfloIntegrationService(string docfloApiUrl, string tenantId, string apiKey)
{
_docfloApiUrl = docfloApiUrl;
_tenantId = tenantId;
_apiKey = apiKey;
_httpClient = new HttpClient();
// Set default headers
_httpClient.DefaultRequestHeaders.Add("x-tenant-id", _tenantId);
_httpClient.DefaultRequestHeaders.Add("apiKey", _apiKey);
}
public bool ConnectToBusinessOne(string server, string companyDB, string username, string password)
{
try
{
_company = new Company();
_company.Server = server;
_company.CompanyDB = companyDB;
_company.UserName = username;
_company.Password = password;
_company.DbServerType = BoDataServerTypes.dst_MSSQL2019; // Adjust as needed
int result = _company.Connect();
if (result != 0)
{
string error = _company.GetLastErrorDescription();
throw new Exception($"Failed to connect to Business One: {error}");
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Connection error: {ex.Message}");
return false;
}
}
public async Task<List<DocfloDocument>> GetDocfloDocuments(string documentType, bool includeResults = true)
{
try
{
string url = $"{_docfloApiUrl}/docs/v1/document?type={documentType}&includeResults={includeResults}";
HttpResponseMessage response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string jsonContent = await response.Content.ReadAsStringAsync();
var docfloResponse = JsonConvert.DeserializeObject<DocfloResponse>(jsonContent);
return docfloResponse.Data;
}
catch (Exception ex)
{
Console.WriteLine($"Error fetching Docflo documents: {ex.Message}");
return new List<DocfloDocument>();
}
}
public bool CreateInvoice(DocfloDocument document)
{
try
{
Documents invoice = (Documents)_company.GetBusinessObject(BoObjectTypes.oInvoices);
// Extract data from Docflo document results
var extractedData = ExtractInvoiceData(document);
// Set customer information
invoice.CardCode = extractedData.CustomerCode;
invoice.CardName = extractedData.CustomerName;
// Set invoice dates
invoice.DocDate = extractedData.InvoiceDate ?? DateTime.Now;
invoice.DocDueDate = extractedData.DueDate ?? DateTime.Now.AddDays(30);
// Set invoice number if available
if (!string.IsNullOrEmpty(extractedData.InvoiceNumber))
{
invoice.NumAtCard = extractedData.InvoiceNumber;
}
// Set tax information
if (!string.IsNullOrEmpty(extractedData.TaxCode))
{
invoice.DocTaxID = extractedData.TaxCode;
}
// Add line items
bool firstLine = true;
foreach (var item in extractedData.LineItems)
{
if (!firstLine)
{
invoice.Lines.Add();
}
invoice.Lines.ItemDescription = item.Description;
invoice.Lines.Quantity = item.Quantity;
invoice.Lines.Price = item.UnitPrice;
invoice.Lines.LineTotal = item.LineTotal;
// Set item code if available
if (!string.IsNullOrEmpty(item.ItemCode))
{
invoice.Lines.ItemCode = item.ItemCode;
}
// Set tax code if available
if (!string.IsNullOrEmpty(item.TaxCode))
{
invoice.Lines.TaxCode = item.TaxCode;
}
firstLine = false;
}
// Set totals
if (extractedData.SubTotal > 0)
{
invoice.DocTotal = extractedData.Total;
}
// Add custom field for Docflo document ID
invoice.UserFields.Fields.Item("U_DocfloID").Value = document.Id;
int result = invoice.Add();
if (result != 0)
{
string error = _company.GetLastErrorDescription();
Console.WriteLine($"Error creating Invoice: {error}");
return false;
}
Console.WriteLine($"Invoice created successfully for document {document.Id}");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Exception creating Invoice: {ex.Message}");
return false;
}
}
private InvoiceData ExtractInvoiceData(DocfloDocument document)
{
var data = new InvoiceData();
data.LineItems = new List<InvoiceLineItem>();
// Extract data from Docflo results
if (document.DocfloResults?.ModelFields?.Items?.Value != null)
{
foreach (var item in document.DocfloResults.ModelFields.Items.Value)
{
var description = item.ValueObject?.Description;
if (description != null)
{
switch (description.Type?.ToLower())
{
case "customer_code":
case "vendor_code":
data.CustomerCode = description.ValueString;
break;
case "customer_name":
case "vendor_name":
case "company_name":
data.CustomerName = description.ValueString;
break;
case "invoice_number":
case "document_number":
data.InvoiceNumber = description.ValueString;
break;
case "invoice_date":
case "document_date":
if (DateTime.TryParse(description.ValueString, out DateTime invoiceDate))
{
data.InvoiceDate = invoiceDate;
}
break;
case "due_date":
case "payment_date":
if (DateTime.TryParse(description.ValueString, out DateTime dueDate))
{
data.DueDate = dueDate;
}
break;
case "tax_code":
case "vat_code":
data.TaxCode = description.ValueString;
break;
case "subtotal":
case "net_amount":
if (double.TryParse(description.ValueString, out double subtotal))
{
data.SubTotal = subtotal;
}
break;
case "total":
case "total_amount":
case "gross_amount":
if (double.TryParse(description.ValueString, out double total))
{
data.Total = total;
}
break;
case "item_description":
case "product_description":
// Create new line item
var lineItem = new InvoiceLineItem
{
Description = description.ValueString,
Quantity = 1, // Default quantity
UnitPrice = 0, // Default price
LineTotal = 0 // Default total
};
data.LineItems.Add(lineItem);
break;
case "quantity":
// Update last line item quantity
if (data.LineItems.Count > 0 && double.TryParse(description.ValueString, out double quantity))
{
data.LineItems[data.LineItems.Count - 1].Quantity = quantity;
}
break;
case "unit_price":
case "price":
// Update last line item price
if (data.LineItems.Count > 0 && double.TryParse(description.ValueString, out double unitPrice))
{
data.LineItems[data.LineItems.Count - 1].UnitPrice = unitPrice;
}
break;
case "line_total":
case "amount":
// Update last line item total
if (data.LineItems.Count > 0 && double.TryParse(description.ValueString, out double lineTotal))
{
data.LineItems[data.LineItems.Count - 1].LineTotal = lineTotal;
}
break;
}
}
}
}
// Generate customer code if not provided
if (string.IsNullOrEmpty(data.CustomerCode) && !string.IsNullOrEmpty(data.CustomerName))
{
data.CustomerCode = GenerateCardCode(data.CustomerName);
}
return data;
}
private string GenerateCardCode(string name)
{
if (string.IsNullOrEmpty(name))
return "CUST" + DateTime.Now.Ticks.ToString().Substring(0, 6);
// Generate card code from name
string cardCode = name.Replace(" ", "").ToUpper();
if (cardCode.Length > 15)
cardCode = cardCode.Substring(0, 15);
return cardCode;
}
public void Disconnect()
{
if (_company != null && _company.Connected)
{
_company.Disconnect();
}
_httpClient?.Dispose();
}
}
// Data models
public class DocfloResponse
{
[JsonProperty("data")]
public List<DocfloDocument> Data { get; set; }
}
public class DocfloDocument
{
[JsonProperty("_id")]
public string Id { get; set; }
[JsonProperty("sourceId")]
public string SourceId { get; set; }
[JsonProperty("sourceDesc")]
public string SourceDesc { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("numOfPages")]
public string NumOfPages { get; set; }
[JsonProperty("createdAt")]
public string CreatedAt { get; set; }
[JsonProperty("docflo_results")]
public DocfloResults DocfloResults { get; set; }
}
public class DocfloResults
{
[JsonProperty("modelFields")]
public ModelFields ModelFields { get; set; }
}
public class ModelFields
{
[JsonProperty("Items")]
public ItemsField Items { get; set; }
}
public class ItemsField
{
[JsonProperty("value")]
public List<Item> Value { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
}
public class Item
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("valueObject")]
public ValueObject ValueObject { get; set; }
}
public class ValueObject
{
[JsonProperty("Description")]
public Description Description { get; set; }
}
public class Description
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("valueString")]
public string ValueString { get; set; }
[JsonProperty("content")]
public string Content { get; set; }
}
public class InvoiceData
{
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public string InvoiceNumber { get; set; }
public DateTime? InvoiceDate { get; set; }
public DateTime? DueDate { get; set; }
public string TaxCode { get; set; }
public double SubTotal { get; set; }
public double Total { get; set; }
public List<InvoiceLineItem> LineItems { get; set; }
}
public class InvoiceLineItem
{
public string ItemCode { get; set; }
public string Description { get; set; }
public double Quantity { get; set; }
public double UnitPrice { get; set; }
public double LineTotal { get; set; }
public string TaxCode { get; set; }
}
// Main program example
class Program
{
static async Task Main(string[] args)
{
var service = new DocfloIntegrationService(
"https://api.docflo.ai", // Replace with actual API URL
"your-tenant-id", // Replace with your tenant ID
"your-api-key" // Replace with your API key
);
// Connect to Business One
bool connected = service.ConnectToBusinessOne(
"your-server", // Business One server
"your-company-db", // Company database
"username", // Username
"password" // Password
);
if (connected)
{
// Get documents from Docflo
var documents = await service.GetDocfloDocuments("invoice", true);
foreach (var document in documents)
{
// Process each document based on type
if (document.Status == "APPROVED")
{
// Create Invoice from processed document
service.CreateInvoice(document);
}
}
service.Disconnect();
}
}
}
}
π§ Configuration Requirementsβ
NuGet Packages Requiredβ
Add these NuGet packages to your project:
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
Business One DI API Referenceβ
Add reference to the SAP Business One DI API:
- SAPbobsCOM.dll (usually located in
C:\Program Files (x86)\SAP\SAP Business One DI API\COM DLL)
Custom Fields Setupβ
Create this custom field in Business One:
- Invoices (OINV):
- Field Name:
U_DocfloID - Type: Text
- Size: 50
- Description: Docflo Document ID for tracking
- Field Name:
Application Configurationβ
Create an app.config file with your settings:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="DocfloApiUrl" value="https://api.docflo.ai" />
<add key="TenantId" value="your-tenant-id" />
<add key="ApiKey" value="your-api-key" />
<add key="B1Server" value="your-b1-server" />
<add key="B1CompanyDB" value="your-company-database" />
<add key="B1Username" value="your-username" />
<add key="B1Password" value="your-password" />
</appSettings>
</configuration>
π Deployment Optionsβ
Option 1: Windows Serviceβ
Deploy as a Windows Service for continuous processing:
- Install the service using
sc createor InstallUtil - Configure service account with appropriate permissions
- Set up logging for monitoring and troubleshooting
- Schedule regular document polling
Option 2: Scheduled Taskβ
Use Windows Task Scheduler for periodic execution:
- Create a scheduled task to run the application
- Set appropriate triggers (hourly, daily, etc.)
- Configure security context for Business One access
- Monitor execution logs
π Monitoring and Loggingβ
Implement comprehensive logging:
// Add to your service class
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(DocfloIntegrationService));
public void LogIntegrationActivity(string message, Exception ex = null)
{
if (ex == null)
{
log.Info(message);
}
else
{
log.Error(message, ex);
}
}
π Integration Complete! Your SAP Business One system is now connected to Docflo.ai and can automatically process invoice documents and create invoices in Business One based on extracted document data.
π Troubleshootingβ
Common Issuesβ
-
DI API Connection Errors:
- Verify Business One server accessibility
- Check user permissions and license availability
- Ensure correct database server type configuration
-
SSL Certificate Issues:
- Verify certificate installation in Windows Certificate Store
- Check certificate chain completeness
- Ensure proper certificate binding
-
API Authentication Failures:
- Validate API key and tenant ID
- Check network connectivity to Docflo.ai
- Verify API endpoint URLs
π Supportβ
For technical assistance with the Business One integration:
- Contact Docflo.ai support team
- Consult your SAP Business One partner
- Review SAP Business One DI API documentation