Priority ERP
This guide walks you through the steps required to connect Docflo.ai to your Priority ERP system, enabling seamless document integration and automated invoice processing.
- Priority ERP system with API access enabled
- Visual Studio or development environment for .NET
- Docflo.ai account with API access
- Priority user with appropriate permissions
- SSL certificate management capabilities
- Priority REST API or OData service configured
π Integration Stepsβ
Follow these steps to establish a secure connection between Docflo.ai and your Priority ERP system:
Step 1: Import SSL Certificate Chainβ
- Download our SSL certificate chain (link)
- Import the certificate to the Windows Certificate Store on your integration server
- Add the certificate to Trusted Root Certification Authorities
- Ensure the certificate is properly validated for HTTPS connections
Step 2: Configure Priority API Accessβ
- Enable REST API services in your Priority system
- Configure API user with appropriate permissions:
- Access to AINVOICES (A/P Invoices) table
- Access to CUSTOMERS table
- Access to PART (Items) table
- Access to DOCUMENTS table
- Set up authentication (Basic Authentication or API Key)
- Test API connectivity using Priority's API documentation
Step 3: Generate API Credentialsβ
- Go to the "Integrations" section in your Docflo.ai platform
- Create an API key for Priority ERP 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 Priority Integration Serviceβ
Create a Windows Service or scheduled application using Priority's REST 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 System.Net.Http.Headers;
using System.Linq;
namespace DocfloPriorityIntegration
{
public class DocfloPriorityIntegrationService
{
private readonly string _docfloApiUrl;
private readonly string _tenantId;
private readonly string _apiKey;
private readonly string _priorityBaseUrl;
private readonly string _priorityUsername;
private readonly string _priorityPassword;
private readonly string _priorityCompany;
private readonly HttpClient _httpClient;
private readonly HttpClient _priorityClient;
public DocfloPriorityIntegrationService(string docfloApiUrl, string tenantId, string apiKey,
string priorityBaseUrl, string priorityUsername, string priorityPassword, string priorityCompany)
{
_docfloApiUrl = docfloApiUrl;
_tenantId = tenantId;
_apiKey = apiKey;
_priorityBaseUrl = priorityBaseUrl;
_priorityUsername = priorityUsername;
_priorityPassword = priorityPassword;
_priorityCompany = priorityCompany;
// Initialize Docflo HTTP client
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("x-tenant-id", _tenantId);
_httpClient.DefaultRequestHeaders.Add("apiKey", _apiKey);
// Initialize Priority HTTP client with basic authentication
_priorityClient = new HttpClient();
var authValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_priorityUsername}:{_priorityPassword}"));
_priorityClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authValue);
_priorityClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
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 async Task<bool> CreateAPInvoice(DocfloDocument document)
{
try
{
// Extract data from Docflo document results
var extractedData = ExtractInvoiceData(document);
// Create A/P Invoice payload for Priority
var invoicePayload = new
{
INVOICEDATE = extractedData.InvoiceDate?.ToString("yyyy-MM-dd") ?? DateTime.Now.ToString("yyyy-MM-dd"),
REFERENCE = extractedData.InvoiceNumber,
SUPPLIER = extractedData.SupplierCode,
SUPNAME = extractedData.SupplierName,
CURDATE = DateTime.Now.ToString("yyyy-MM-dd"),
TOTAL = extractedData.Total,
TOTPRICE = extractedData.Total,
CURRENCY = extractedData.CurrencyCode ?? "USD",
DETAILS = $"Invoice processed from Docflo document {document.Id}",
// Custom field for Docflo tracking
UFIELD1 = document.Id, // Assuming UFIELD1 is available for custom data
AINVOICEITEMS = extractedData.LineItems.Select((line, index) => new
{
KLINE = index + 1,
PARTNAME = line.ItemCode ?? line.Description,
TQUANT = line.Quantity,
PRICE = line.UnitPrice,
TOTPRICE = line.LineTotal,
PERCENT = 0, // Tax percentage if applicable
DETAILS = line.Description
}).ToArray()
};
string jsonPayload = JsonConvert.SerializeObject(invoicePayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
string priorityUrl = $"{_priorityBaseUrl}/odata/Priority/{_priorityCompany}/AINVOICES";
HttpResponseMessage response = await _priorityClient.PostAsync(priorityUrl, content);
if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"A/P Invoice created successfully for document {document.Id}");
Console.WriteLine($"Response: {responseContent}");
return true;
}
else
{
string errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating A/P Invoice: {response.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception creating A/P Invoice: {ex.Message}");
return false;
}
}
public async Task<bool> CreateARInvoice(DocfloDocument document)
{
try
{
// Extract data from Docflo document results
var extractedData = ExtractInvoiceData(document);
// Create A/R Invoice payload for Priority
var invoicePayload = new
{
INVOICEDATE = extractedData.InvoiceDate?.ToString("yyyy-MM-dd") ?? DateTime.Now.ToString("yyyy-MM-dd"),
REFERENCE = extractedData.InvoiceNumber,
CUST = extractedData.CustomerCode,
CUSTNAME = extractedData.CustomerName,
CURDATE = DateTime.Now.ToString("yyyy-MM-dd"),
TOTAL = extractedData.Total,
TOTPRICE = extractedData.Total,
CURRENCY = extractedData.CurrencyCode ?? "USD",
DETAILS = $"Invoice processed from Docflo document {document.Id}",
// Custom field for Docflo tracking
UFIELD1 = document.Id, // Assuming UFIELD1 is available for custom data
INVOICEITEMS = extractedData.LineItems.Select((line, index) => new
{
KLINE = index + 1,
PARTNAME = line.ItemCode ?? line.Description,
TQUANT = line.Quantity,
PRICE = line.UnitPrice,
TOTPRICE = line.LineTotal,
PERCENT = 0, // Tax percentage if applicable
DETAILS = line.Description
}).ToArray()
};
string jsonPayload = JsonConvert.SerializeObject(invoicePayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
string priorityUrl = $"{_priorityBaseUrl}/odata/Priority/{_priorityCompany}/INVOICES";
HttpResponseMessage response = await _priorityClient.PostAsync(priorityUrl, content);
if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"A/R Invoice created successfully for document {document.Id}");
Console.WriteLine($"Response: {responseContent}");
return true;
}
else
{
string errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating A/R Invoice: {response.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception creating A/R Invoice: {ex.Message}");
return false;
}
}
public async Task<string> GetOrCreateCustomer(string customerName, string customerCode = null)
{
try
{
// First, try to find existing customer
string searchUrl = $"{_priorityBaseUrl}/odata/Priority/{_priorityCompany}/CUSTOMERS";
if (!string.IsNullOrEmpty(customerCode))
{
searchUrl += $"?$filter=CUST eq '{customerCode}'";
}
else if (!string.IsNullOrEmpty(customerName))
{
searchUrl += $"?$filter=CUSTNAME eq '{customerName}'";
}
HttpResponseMessage searchResponse = await _priorityClient.GetAsync(searchUrl);
if (searchResponse.IsSuccessStatusCode)
{
string searchContent = await searchResponse.Content.ReadAsStringAsync();
var searchResult = JsonConvert.DeserializeObject<PriorityODataResponse<PriorityCustomer>>(searchContent);
if (searchResult.Value?.Any() == true)
{
return searchResult.Value.First().CUST;
}
}
// Customer not found, create new one
var customerPayload = new
{
CUST = customerCode ?? GenerateCustomerCode(customerName),
CUSTNAME = customerName ?? "Unknown Customer",
PHONE = "",
EMAIL = "",
ADDRESS = "",
STATE = "",
ZIP = "",
COUNTRY = ""
};
string jsonPayload = JsonConvert.SerializeObject(customerPayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
string createUrl = $"{_priorityBaseUrl}/odata/Priority/{_priorityCompany}/CUSTOMERS";
HttpResponseMessage createResponse = await _priorityClient.PostAsync(createUrl, content);
if (createResponse.IsSuccessStatusCode)
{
Console.WriteLine($"Customer created: {customerPayload.CUST}");
return customerPayload.CUST;
}
else
{
string errorContent = await createResponse.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating customer: {createResponse.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return customerCode ?? GenerateCustomerCode(customerName);
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception in GetOrCreateCustomer: {ex.Message}");
return customerCode ?? GenerateCustomerCode(customerName);
}
}
private string GenerateCustomerCode(string customerName)
{
if (string.IsNullOrEmpty(customerName))
return "CUST" + DateTime.Now.Ticks.ToString().Substring(0, 6);
// Generate customer code from name
string custCode = customerName.Replace(" ", "").ToUpper();
if (custCode.Length > 10)
custCode = custCode.Substring(0, 10);
return custCode;
}
private PriorityInvoiceData ExtractInvoiceData(DocfloDocument document)
{
var data = new PriorityInvoiceData();
data.LineItems = new List<PriorityInvoiceLineItem>();
// 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 "supplier_code":
case "vendor_code":
data.SupplierCode = description.ValueString;
break;
case "supplier_name":
case "vendor_name":
data.SupplierName = description.ValueString;
break;
case "customer_code":
data.CustomerCode = description.ValueString;
break;
case "customer_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 "currency_code":
case "currency":
data.CurrencyCode = description.ValueString;
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":
case "line_description":
// Create new line item
var lineItem = new PriorityInvoiceLineItem
{
Description = description.ValueString,
Quantity = 1, // Default quantity
UnitPrice = 0, // Default price
LineTotal = 0 // Default total
};
data.LineItems.Add(lineItem);
break;
case "item_code":
case "part_number":
// Update last line item code
if (data.LineItems.Count > 0)
{
data.LineItems[data.LineItems.Count - 1].ItemCode = description.ValueString;
}
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/supplier codes if not provided
if (string.IsNullOrEmpty(data.CustomerCode) && !string.IsNullOrEmpty(data.CustomerName))
{
data.CustomerCode = GenerateCustomerCode(data.CustomerName);
}
if (string.IsNullOrEmpty(data.SupplierCode) && !string.IsNullOrEmpty(data.SupplierName))
{
data.SupplierCode = GenerateCustomerCode(data.SupplierName);
}
return data;
}
public void Dispose()
{
_httpClient?.Dispose();
_priorityClient?.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 PriorityODataResponse<T>
{
[JsonProperty("value")]
public List<T> Value { get; set; }
}
public class PriorityCustomer
{
[JsonProperty("CUST")]
public string CUST { get; set; }
[JsonProperty("CUSTNAME")]
public string CUSTNAME { get; set; }
}
public class PriorityInvoiceData
{
public string SupplierCode { get; set; }
public string SupplierName { get; set; }
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public string InvoiceNumber { get; set; }
public DateTime? InvoiceDate { get; set; }
public string CurrencyCode { get; set; }
public double Total { get; set; }
public List<PriorityInvoiceLineItem> LineItems { get; set; }
}
public class PriorityInvoiceLineItem
{
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; }
}
// Main program example
class Program
{
static async Task Main(string[] args)
{
var service = new DocfloPriorityIntegrationService(
"https://api.docflo.ai", // Replace with actual Docflo API URL
"your-tenant-id", // Replace with your tenant ID
"your-api-key", // Replace with your API key
"https://your-priority-server", // Replace with your Priority server URL
"priority-username", // Replace with Priority username
"priority-password", // Replace with Priority password
"your-company-code" // Replace with your Priority company code
);
try
{
// Get documents from Docflo
var documents = await service.GetDocfloDocuments("invoice", true);
foreach (var document in documents)
{
// Process each document based on status
if (document.Status == "APPROVED")
{
// Create A/P Invoice for vendor invoices
bool apResult = await service.CreateAPInvoice(document);
// Or create A/R Invoice for customer invoices
// bool arResult = await service.CreateARInvoice(document);
if (apResult)
{
Console.WriteLine($"Successfully processed document {document.Id}");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error in main process: {ex.Message}");
}
finally
{
service.Dispose();
}
}
}
}
π§ 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" />
Priority ERP User Permissionsβ
Ensure your integration user has the following permissions:
- A/P Invoices (AINVOICES) - Read/Write access
- A/R Invoices (INVOICES) - Read/Write access
- Customers (CUSTOMERS) - Read/Write access
- Suppliers (SUPPLIERS) - Read/Write access
- Items (PART) - Read access
- API Access - REST API or OData service access
Priority Custom Fields Setupβ
Create custom fields in Priority for tracking Docflo documents:
-
AINVOICES Table:
- Field Name:
UFIELD1 - Type: Text (50)
- Description: Docflo Document ID
- Field Name:
-
INVOICES Table:
- Field Name:
UFIELD1 - Type: Text (50)
- Description: Docflo Document ID
- 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="PriorityBaseUrl" value="https://your-priority-server" />
<add key="PriorityUsername" value="your-priority-username" />
<add key="PriorityPassword" value="your-priority-password" />
<add key="PriorityCompany" value="your-company-code" />
</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 Priority access
- Monitor execution logs
Option 3: Azure Functionβ
Deploy as an Azure Function for cloud-based processing:
- Create Azure Function App
- Configure timer trigger for scheduled execution
- Set application settings for configuration values
- Monitor execution through Azure portal
π Monitoring and Loggingβ
Implement comprehensive logging using NLog or Serilog:
// Add to your service class
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
public void LogIntegrationActivity(string message, Exception ex = null)
{
if (ex == null)
{
logger.Info(message);
}
else
{
logger.Error(ex, message);
}
}
π Priority ERP Specific Considerationsβ
Table Structureβ
Priority ERP uses specific table structures:
- AINVOICES - Accounts Payable Invoices
- INVOICES - Accounts Receivable Invoices
- CUSTOMERS - Customer master data
- SUPPLIERS - Supplier master data
- PART - Item master data
Field Naming Conventionβ
Priority uses uppercase field names:
INVOICEDATE- Invoice dateREFERENCE- Invoice reference numberSUPPLIER- Supplier codeCUST- Customer codeTOTPRICE- Total price
OData Serviceβ
Priority supports OData for REST API access:
- Base URL:
https://your-server/odata/Priority/CompanyCode/ - Authentication: Basic Authentication
- Content-Type:
application/json
π Integration Complete! Your Priority ERP system is now connected to Docflo.ai and can automatically process invoice documents, creating both A/P and A/R invoices based on extracted document data.
π Troubleshootingβ
Common Issuesβ
-
API Authentication Errors:
- Verify username and password credentials
- Check user permissions in Priority
- Ensure API services are enabled
-
SSL Certificate Issues:
- Verify certificate installation in Windows Certificate Store
- Check certificate chain completeness
- Ensure proper certificate binding
-
OData Service Issues:
- Verify OData service is configured and running
- Check company code in URL
- Ensure proper HTTP headers
-
Data Mapping Issues:
- Validate extracted data from Docflo documents
- Check required fields for Priority invoice creation
- Ensure proper data type conversions
-
Table Access Issues:
- Verify user has access to required tables
- Check table permissions in Priority
- Ensure custom fields exist
π Supportβ
For technical assistance with the Priority ERP integration:
- Contact Docflo.ai support team
- Consult Priority ERP documentation
- Review Priority API guides
- Contact your Priority implementation partner