Skip to main content

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.

πŸ“‹ Prerequisites:
  • 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​

  1. Download our SSL certificate chain (link)
  2. Import the certificate to the Windows Certificate Store on your integration server
  3. Add the certificate to Trusted Root Certification Authorities
  4. Ensure the certificate is properly validated for HTTPS connections

Step 2: Configure Priority API Access​

  1. Enable REST API services in your Priority system
  2. 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
  3. Set up authentication (Basic Authentication or API Key)
  4. Test API connectivity using Priority's API documentation

Step 3: Generate API Credentials​

  1. Go to the "Integrations" section in your Docflo.ai platform
  2. Create an API key for Priority ERP integration
  3. Copy the API key and store it securely
  4. 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:

  1. A/P Invoices (AINVOICES) - Read/Write access
  2. A/R Invoices (INVOICES) - Read/Write access
  3. Customers (CUSTOMERS) - Read/Write access
  4. Suppliers (SUPPLIERS) - Read/Write access
  5. Items (PART) - Read access
  6. API Access - REST API or OData service access

Priority Custom Fields Setup​

Create custom fields in Priority for tracking Docflo documents:

  1. AINVOICES Table:

    • Field Name: UFIELD1
    • Type: Text (50)
    • Description: Docflo Document ID
  2. INVOICES Table:

    • Field Name: UFIELD1
    • Type: Text (50)
    • Description: Docflo Document ID

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:

  1. Install the service using sc create or InstallUtil
  2. Configure service account with appropriate permissions
  3. Set up logging for monitoring and troubleshooting
  4. Schedule regular document polling

Option 2: Scheduled Task​

Use Windows Task Scheduler for periodic execution:

  1. Create a scheduled task to run the application
  2. Set appropriate triggers (hourly, daily, etc.)
  3. Configure security context for Priority access
  4. Monitor execution logs

Option 3: Azure Function​

Deploy as an Azure Function for cloud-based processing:

  1. Create Azure Function App
  2. Configure timer trigger for scheduled execution
  3. Set application settings for configuration values
  4. 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:

  1. AINVOICES - Accounts Payable Invoices
  2. INVOICES - Accounts Receivable Invoices
  3. CUSTOMERS - Customer master data
  4. SUPPLIERS - Supplier master data
  5. PART - Item master data

Field Naming Convention​

Priority uses uppercase field names:

  • INVOICEDATE - Invoice date
  • REFERENCE - Invoice reference number
  • SUPPLIER - Supplier code
  • CUST - Customer code
  • TOTPRICE - 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​

  1. API Authentication Errors:

    • Verify username and password credentials
    • Check user permissions in Priority
    • Ensure API services are enabled
  2. SSL Certificate Issues:

    • Verify certificate installation in Windows Certificate Store
    • Check certificate chain completeness
    • Ensure proper certificate binding
  3. OData Service Issues:

    • Verify OData service is configured and running
    • Check company code in URL
    • Ensure proper HTTP headers
  4. Data Mapping Issues:

    • Validate extracted data from Docflo documents
    • Check required fields for Priority invoice creation
    • Ensure proper data type conversions
  5. 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