> >
|

Financial Force Accounting: Company Setup

When you have to do custom work in Apex to automate things on FinancialForce Accounting (FFA), you are likely to get error messages telling you that you don’t have any current companies. Even when you’re not working directly with FFA module, you may need to eventually setup an FFA company in Apex. For example, when working with FinancialForce Supply Chain Management (SCM) in an integration with FFA, you have to set the company on the sales orders and purchase orders in SCM in order to push SCM invoice into FFA sales invoice, and SCM AP Vouchers into FFA Payable Invoices.

FFA companies are represented both by records of the custom object ‘Company’ and by User Queues. It’s usually recommended that companies be created in the Accounting Quickstart process. This is a user-friendly process that speeds up the configuration of FFA by creating companies and much more, but for the purpose of this article, we will focus on companies. We will also assume that you already have at least one valid company properly set up by the Accounting Quickstart process to follow the explanations.

We will first look at the company record(s) in the custom object: go to the + sign on your SalesForce menu to see all tabs, and click on the Company tab. Open any existing valid company. Notice the owner of this company record – regardless of who has run the Accounting Quickstart process, this user won’t be the owner of the company record. Instead, you are likely to see something like ‘FF_YourCompanyName’. This is the User Queue associated to this company.

Now go to Setup/Manage Users/Queue. Look for ‘FF_YourCompanyName’. Notice it has a lot of supported objects such as year, sales invoice or journal. Open this queue, and notice there might be a list of users that are members of this queue. These users currently have this company selected as their current company. They might be member of more than one company queue (multi-company mode), depending of their access to the different companies in the organization. Their access to select different companies as their current company is defined in the custom object ‘User Company’. If there are some users who cannot select different companies due to a lack of an FFA license but still need to be related to an FFA company (for example, sales reps doing customer quotations in CPQ and then turning them into sales orders: they typically do not have an FFA license, but there is still a need for specifying the company on the sales orders records they create), they can be directly added to the appropriate queue here. If the users would need to be able to select different companies without access to FFA, more customization would be needed.

When working on a sandbox, it is possible to have existing queues from the production organization, except there is no Company and User Company records to go with it. This is because the Company and User Company are records of non-setup objects, which are usually not replicated in sandbox (unless you create a ‘full’ sandbox). The queues, however, are setup objects – so they are replicated just like users or security profiles. The fact that FFA companies are a queue makes it a little more complex to handle in Apex, but it is feasible, which makes it feasible also to avoid running test with the nasty @isTest(seeAllData=true) annotation. However, you will need to change context between DML operations on setup objects and non-setup objects using System.runAs(someUser), or to use the @future annotation to perform the DML operations in different transactions.

With this blog post you will find a trigger that sets the current user’s current company on a purchase order (you can easily replicate this for the sales order), its test class, and the test data factory that creates the company for the test class. Please do not blindly copy-paste: it currently works as is on most of our implementations, however depending on the customizations that might have been done in your organization, you might need to adapt it. For example, on one of our implementations, we had to remove the error message of the trigger.

Now let’s have a look at the trigger:

trigger SetCurrentCompanyOnSCMNewPO on SCMC__Purchase_Order__c (before insert) {
public static final string MISSING_CURRENT_COMPANY_EXCEPTION=’You must first select a current company, and only one, in FinancialForce Accounting, before creating purchase order(s)’;
private Id currentUserCompany = null;
List currentCompanyList = [SELECT Id, name
FROM c2g__codaCompany__c
WHERE OwnerId IN (SELECT GroupId FROM GroupMember where userorgroupid = :UserInfo.getUserId())];
if (currentCompanyList != null && currentCompanyList.size() == 1) {
currentUserCompany = currentCompanyList.get(0).Id;
}
for(SCMC__Purchase_Order__c purchaseOrder : Trigger.new) {
if (purchaseOrder.SCMFFA__Company__c == null) {
if (currentUserCompany != null) {
purchaseOrder.SCMFFA__Company__c = currentUserCompany;
} else {
purchaseOrder.addError(MISSING_CURRENT_COMPANY_EXCEPTION);
}
}
}
}

 

Here, we look for a company record whose owner id is a queue in which the current user is a member. This means we are looking for this user’s current company, if any. Multi-companies mode is possible in FFA when it comes to viewing records or reports, but you must have only one current company selected when you create records. This is why we check if we get one and only one result from this query. If there is no result, the current user has no current company, if there is more than one result, the current user is in multi-companies mode. We then write this company on the purchase order if the user has not already specified one when filling the object form.
Let’s have a look at the test data factory now, which creates a FFA company:

 

@isTest
public class SetCurrentCompany_TEST_DataFactory {
//this list of SObjectType to add to the queue might need to be updated when upgrading FFA – verify, in existing data, user queue’s definition after upgrade
public static final List SOBJECT_TYPES_API_NAMES=new List {‘c2g__AsOfAging__c’,’c2g__CancelPayment__c’,’c2g__ReportingBalance__c’,’c2g__codaAccountingCurrency__c’,’c2g__codaBankAccount__c’,’c2g__codaBankReconciliation__c’,’c2g__codaBankStatement__c’,’c2g__codaBudget__c’,’c2g__codaCashEntry__c’,’c2g__codaCashMatchingHistory__c’,’c2g__codaCompany__c’,’c2g__codaCreditNote__c’,’c2g__codaCurrencyRevaluationGroup__c’,’c2g__codaCurrencyRevaluation__c’,’c2g__codaGroupingReference__c’,’c2g__codaIntercompanyDefinition__c’,’c2g__codaIntercompanyTransfer__c’,’c2g__codaInvoice__c’,’c2g__codaJournal__c’,’c2g__codaMatchingReference__c’,’c2g__codaPaymentMediaControl__c’,’c2g__codaPaymentMediaSummary__c’,’c2g__codaPaymentTemplate__c’,’c2g__codaPayment__c’,’c2g__codaPurchaseCreditNote__c’,’c2g__codaPurchaseInvoice__c’,’c2g__codaTextDefinition__c’,’c2g__codaTransaction__c’,’c2g__codaYear__c’};
public static c2g__codaCompany__c createCurrentCompany(String CompanyName, User SomeUser, String recordType, Boolean selectAsCurrent, String taxRegistrationNumber){
//create the company user queue
Group companyOwnerQueue = new Group(Type=’Queue’, Name=CompanyName);
System.runAs(SomeUser){ //to avoid mixed dml error
insert companyOwnerQueue;
//add SObjectType to be owned by the company user queue instead of its individual queue members (users)
List queueSObjectList=new List ();
for(String sobjectTypeAPIName: SOBJECT_TYPES_API_NAMES){
QueueSObject queuesObjectTypeToAdd = new QueueSObject(SobjectType=sobjectTypeAPIName, QueueId=companyOwnerQueue.Id);
queueSObjectList.add(queuesObjectTypeToAdd);
}
insert queueSObjectList;
//Have the user select this queue as his current company
if(selectAsCurrent){
insert new GroupMember(GroupId=companyOwnerQueue.Id, UserOrGroupId=someUser.ID);
}
}
//create the company
c2g__codaCompany__c company=new c2g__codaCompany__c(Name=companyName, c2g__VATRegistrationNumber__c=taxRegistrationNumber,OwnerId=companyOwnerQueue.Id);
if(recordType!=null){
company.RecordTypeId=recordType;
}
System.runAs(SomeUser){
insert company;
//give the user access to this company (optional…)
c2g__codaUserCompany__c userCompany=new c2g__codaUserCompany__c(c2g__Company__c=company.Id, c2g__User__c=SomeUser.Id);
insert userCompany;
}
return company;
}
}

 

Here the taxRegistrationNumber is not mandatory, we just happened to need it for printing it out in some VisualForce templates. Up until very recently we didn’t bother specifying a record type, but with the latest release of FFA v13 there is now a new company record type called ‘Combined’ to support Canadian taxes, so for some customers we do need to specify this too.

We can then reference this test data factory in the test class for the purchase order trigger, where we test 5 scenarios. The first 3 scenarios return an error: the current user has no current company, the current user is member of a queue which is not a company queue, or the current user is in multi-companies mode. The fourth scenario tests if the user already specified a company on the new purchase order before saving it – in this case, we do not want to overwrite it. Finally, the last scenario verifies that if the current user has one company selected as his current company, it gets written on the purchase order.

 

@isTest
public class SetCurrentCompanyOnSCMNewPO_TEST {
private static final string COMPANY_NAME=’Test Company Name’;
private static final string ANOTHER_COMPANY_NAME=’Test Another Company Name’;
private static final string GROUP_TYPE=’Queue’;
private static final string GROUP_NAME=’Test Queue’;
private static final string SUPPLIER_SITE_NAME=’Test Supplier Site Name’;
private static final string SUPPLIER_SITE_EMAIL=’vicky.pomerleau@bigbangerp.com’;
private static final string SUPPLIER_SITE_PREFERRED_COMMUNICATION=’E-mail’;
private static final string CORPORATE_CURRENCY_NAME=’CAD’;
private static final Boolean SELECT_AS_CURRENT_COMPANY=true;
private static final string MISSING_CURRENT_COMPANY_EXCEPTION=’You must first select a current company, and only one, in FinancialForce Accounting, before creating purchase order(s)’;
private static final String TAX_REGISTRATION_NUMBER=’1234567890′;
public static @isTest void givenCurrentUserHasNoCurrentCompanyWhenInsertingNewPurchaseOrderThenTriggerThrowsException(){
SCMC__Supplier_Site__c supplierSite=createSomeSupplierSite();
try{
SCMC__Purchase_Order__c purchaseOrder=new SCMC__Purchase_Order__c(SCMC__Supplier_Site__c=supplierSite.Id,
SCMC__Purchase_Order_Date__c=Date.today());
insert purchaseOrder;
}catch(Exception e){
Boolean expectedExceptionThrown = e.getMessage().contains(MISSING_CURRENT_COMPANY_EXCEPTION) ? true : false;
System.AssertEquals(expectedExceptionThrown, true);
}
}
public static @isTest void givenCurrentUserHasNoCurrentCompanyButStillIsInAQueueWhenInsertingNewPurchaseOrderThenTriggerThrowsException(){
User currentUser=new User(Id=UserInfo.getUserId());
Group someQueue = new Group(Type=GROUP_TYPE, Name=GROUP_NAME);
insert someQueue;
System.runAs(currentUser){ //to avoid mixed dml error
//Put the user in the queue
insert new GroupMember(GroupId=someQueue.Id, UserOrGroupId=currentUser.ID);
}
SCMC__Supplier_Site__c supplierSite=createSomeSupplierSite();
try{
SCMC__Purchase_Order__c purchaseOrder=new SCMC__Purchase_Order__c(SCMC__Supplier_Site__c=supplierSite.Id,
SCMC__Purchase_Order_Date__c=Date.today());
insert purchaseOrder;
}catch(Exception e){
Boolean expectedExceptionThrown = e.getMessage().contains(MISSING_CURRENT_COMPANY_EXCEPTION) ? true : false;
System.AssertEquals(expectedExceptionThrown, true);
}
}
public static @isTest void givenCurrentUserIsInMultiCompanyModeWhenInsertingNewPurchaseOrderThenTriggerThrowsException(){
User currentUser=new User(Id=UserInfo.getUserId());
c2g__codaCompany__c company=SetCurrentCompany_TEST_DataFactory.createCurrentCompany(COMPANY_NAME,currentUser,null, SELECT_AS_CURRENT_COMPANY, TAX_REGISTRATION_NUMBER);
c2g__codaCompany__c another_company=SetCurrentCompany_TEST_DataFactory.createCurrentCompany(ANOTHER_COMPANY_NAME,currentUser,null, SELECT_AS_CURRENT_COMPANY, TAX_REGISTRATION_NUMBER);
System.runAs(currentUser){
SCMC__Supplier_Site__c supplierSite=createSomeSupplierSite();
try{
SCMC__Purchase_Order__c purchaseOrder=new SCMC__Purchase_Order__c(SCMC__Supplier_Site__c=supplierSite.Id,
SCMC__Purchase_Order_Date__c=Date.today());
insert purchaseOrder;
}catch(Exception e){
Boolean expectedExceptionThrown = e.getMessage().contains(MISSING_CURRENT_COMPANY_EXCEPTION) ? true : false;
System.AssertEquals(expectedExceptionThrown, true);
}
}
}
public static @isTest void givenCurrentUserHasAlreadySpecifiedCompanyWhenInsertingNewPurchaseOrderThenUserCurrentCompanyIsNotAddedOnPO(){
User currentUser=new User(Id=UserInfo.getUserId());
c2g__codaCompany__c company=SetCurrentCompany_TEST_DataFactory.createCurrentCompany(COMPANY_NAME,currentUser,null, SELECT_AS_CURRENT_COMPANY, TAX_REGISTRATION_NUMBER);
c2g__codaCompany__c another_company=SetCurrentCompany_TEST_DataFactory.createCurrentCompany(ANOTHER_COMPANY_NAME,currentUser,null,!SELECT_AS_CURRENT_COMPANY, TAX_REGISTRATION_NUMBER);
System.runAs(currentUser){
SCMC__Supplier_Site__c supplierSite=createSomeSupplierSite();
SCMC__Purchase_Order__c purchaseOrder=new SCMC__Purchase_Order__c(SCMC__Supplier_Site__c=supplierSite.Id,
SCMFFA__Company__c=another_company.Id,
SCMC__Purchase_Order_Date__c=Date.today());
insert purchaseOrder;
SCMC__Purchase_Order__c insertedPurchaseOrder=[SELECT SCMFFA__Company__r.Name FROM SCMC__Purchase_Order__c WHERE Id=:purchaseOrder.Id LIMIT 1];
boolean currentCompanyIsNotInsertedOnPurchaseOrder=ANOTHER_COMPANY_NAME.compareTo(insertedPurchaseOrder.SCMFFA__Company__r.Name)==0;
System.Assert(currentCompanyIsNotInsertedOnPurchaseOrder);
}
}
public static @isTest void givenCurrentUserHasCurrentCompanyWhenInsertingNewPurchaseOrderThenUserCurrentCompanyIsAddedOnPO(){
User currentUser=new User(Id=UserInfo.getUserId());
c2g__codaCompany__c company=SetCurrentCompany_TEST_DataFactory.createCurrentCompany(COMPANY_NAME,currentUser,null, SELECT_AS_CURRENT_COMPANY, TAX_REGISTRATION_NUMBER);
System.runAs(currentUser){
SCMC__Supplier_Site__c supplierSite=createSomeSupplierSite();
SCMC__Purchase_Order__c purchaseOrder=new SCMC__Purchase_Order__c(SCMC__Supplier_Site__c=supplierSite.Id,
SCMC__Purchase_Order_Date__c=Date.today());
insert purchaseOrder;
SCMC__Purchase_Order__c insertedPurchaseOrder=[SELECT SCMFFA__Company__r.Name FROM SCMC__Purchase_Order__c WHERE Id=:purchaseOrder.Id LIMIT 1];
boolean currentCompanyIsInsertedOnPurchaseOrder=COMPANY_NAME.compareTo(insertedPurchaseOrder.SCMFFA__Company__r.Name)==0;
System.Assert(currentCompanyIsInsertedOnPurchaseOrder);
}
}
//test setup method
private static SCMC__Supplier_Site__c createSomeSupplierSite(){
SCMC__Currency_Master__c corporateCurrency=new SCMC__Currency_Master__c(Name=CORPORATE_CURRENCY_NAME,
SCMC__Number_of_decimals__c=2,
SCMC__Active__c=true,
SCMC__Corporate_Currency__c=true);
insert corporateCurrency;
SCMC__Supplier_Site__c supplierSite= new SCMC__Supplier_Site__c(Name=SUPPLIER_SITE_NAME,
SCMC__Currency__c=corporateCurrency.Id,
SCMC__Preferred_Communication_Purchase_Order__c=SUPPLIER_SITE_PREFERRED_COMMUNICATION,
SCMC__E_Mail__c=SUPPLIER_SITE_EMAIL);
insert supplierSite;
return supplierSite;
}
}

 

Vicky Pomerleau
Vicky joined Big Bang ERP after years of experience in a variety of industries such as finance and software development. As a Senior Consultant, Vicky brings to the table her top notch development skills as well as the ability to interface effectively and efficiently with customers to discuss their business needs.