How to create a custom invoice layout
Default template
GoMeddo Subscription Management provides you want a standard layout which has limited customisation options. For now, you can:
Upload your logo in the administration
Upload your footer image in the administration
Change the terms and condition URL
You need to upload a file to the attachments of the Administration, that is prefixed with Logo_ or Footer_ so GoMeddo Subscription Management will know which images to capture.
The layout of the default template is as follows:
Custom template
To create a custom Invoice Layout you need to create a visualforce page that is rendered as a PDF. This page needs to have the GoMeddo Subscription Management Invoice object as it standardController. You can extend this with a custom class to get additional functionalities in the pdf.
<apex:page standardController="Subscription25__Invoice__c" renderAs="pdf" sidebar="false" showHeader="false" applyBodyTag="false">
Invoice Date: {!Subscription25__Invoice__c.Subscription25__Invoice_Date__c}
etc..
</apex:page>
To use this template you must create an “Invoice template record” in GoMeddo Subscription Management and assign it to the administrations in which you want to use this template. This allows you to create different layouts for different administrations.
The Name of the Invoice Layout is your reference to this record.
The page name is the API name of your VisualforcePage. Don’t forget to add the c__ before the name.
One record can be the fallback record which will be used if there is no layout assigned to the administration.
It is important to start the Page name with c__ to indicate to Salesforce that this is a custom namespace visualforce page. The full name of the item will be c__<VisualForceApiName>.
On your administration, you should have a related list called Administration Invoice Layouts in which you should create a record to link this template to that administration.
You can also use the following template
You need to do three steps:
Upload static resource
Create Apex Class + Test class
Create Visualforce page
Upload the following static resource with the name ‘invoice’
Class Ctrl_InvoiceLayout
:
public with sharing class Ctrl_InvoiceLayout {
private Subscription25__Invoice__c invoice;
public InvoiceItem[] items {get; private set;}
public Id companyLogoId {get; private set;}
public Decimal sum {get; private set;}
public Decimal total {get; private set;}
public Decimal taxAmount {get; private set;}
public Boolean hasICP {get; private set;}
public Ctrl_InvoiceLayout(ApexPages.StandardController stdController) {
this.invoice = (Subscription25__Invoice__c)stdController.getRecord();
List<Order> orders = this.getOrders();
this.items = new InvoiceItem[]{};
this.hasICP = false;
for (Order order : orders) {
for (OrderItem item : order.OrderItems) {
this.items.add(new InvoiceItem(item));
this.hasICP = this.hasICP || item.Subscription25__Use_ICP__c;
}
}
this.taxAmount = 0;
this.sum = 0;
for (InvoiceItem item : this.items) {
this.sum += item.price * item.quantity;
this.taxAmount += item.price * item.quantity * item.tax / 100;
}
this.sum = Ctrl_InvoiceLayout.format(this.sum);
this.taxAmount = Ctrl_InvoiceLayout.format(this.taxAmount);
this.total = Ctrl_InvoiceLayout.format(this.sum + this.taxAmount);
//try to load an attachment of the administration that starts with logo, if present, use that as a logo, otherwise, fall back
//to the company logo as defined in the custom setting
List<Attachment> atts = this.getAttachments();
this.companyLogoId = atts.size() > 0 ? atts.get(0).Id : Ctrl_InvoiceLayout.getLogoId();
}
private List<Attachment> getAttachments() {
Id adminId = this.invoice.Subscription25__Administration__c;
String query = 'SELECT Name FROM Attachment WHERE ParentId = :adminId AND Name LIKE \'Logo%\'';
return Database.query(query);
}
private List<Order> getOrders() {
Id invoiceId = this.invoice.Id;
return [
SELECT Name, AccountId, Account.Name,
Subscription25__Reseller_Customer__c, Subscription25__Reseller_Customer__r.Name,
Subscription25__Invoice__r.Subscription25__Account__r.Name,
(
SELECT Id, Subscription25__VAT_Percentage__c, Quantity,
UnitPrice, Description, Subscription25__Use_ICP__c,
Subscription25__Hide_VAT_On_Invoice__c, Subscription25__Product__c, Subscription25__Product__r.Name,
OrderId, Order.Name, Order.Id, Order.AccountId, Order.Account.Name,
Order.Subscription25__Reseller_Customer__c, Order.Subscription25__Reseller_Customer__r.Name,
Order.Subscription25__Invoice__c, Order.Subscription25__Invoice__r.Subscription25__Account__c, Order.Subscription25__Invoice__r.Subscription25__Account__r.Name
FROM OrderItems
)
FROM Order
WHERE Subscription25__Invoice__c = :invoiceId
];
}
private static Decimal format(Decimal amount) {
return amount.setScale(2, RoundingMode.HALF_UP);
}
private static Id getLogoId(){
String query = 'SELECT Id FROM Document WHERE DeveloperName LIKE \'%Subscription25_Logo\' LIMIT 1';
List<Document> docs = Database.query(query);
return docs.size() > 0 ? docs[0].Id : null;
}
private class InvoiceItem {
public Decimal quantity {get; private set;}
public String accountLabel {get; private set;} //used for resellers and bill to parent
public String label {get; private set;}
public Decimal price {get; private set;}
public Decimal tax {get; private set;}
public String description {get; private set;}
public InvoiceItem(OrderItem oi) {
this.quantity = oi.Quantity;
this.label = oi.Subscription25__Product__r.Name;
this.price = oi.UnitPrice;
this.tax = oi.Subscription25__VAT_Percentage__c;
this.description = oi.Description;
if(oi.Order.Subscription25__Reseller_Customer__c != null){
this.accountLabel = oi.Order.Subscription25__Reseller_Customer__r.Name;
}
if(oi.Order.AccountId != oi.Order.Subscription25__Invoice__r.Subscription25__Account__c){
this.accountLabel = oi.Order.Account.Name;
}
}
public Decimal getTotal() {
Decimal total = this.price * this.quantity;
return Ctrl_InvoiceLayout.format(total);
}
}
}
Test Class Test_Ctrl_InvoiceLayout
:
@isTest
private class Test_Ctrl_InvoiceLayout {
@testSetup static void setup(){
String entity = 'GoMeddo Subscription Management';
Subscription25__Administration__c admin = new Subscription25__Administration__c(
Subscription25__Billing_Email_Address__c = 's25@test.com',
Subscription25__Billing_Name__c = entity,
Subscription25__Billing_Street__c = 'teststraat',
Subscription25__Billing_Postal_Code__c = '1234ab',
Subscription25__Billing_City__c = 'Amsterdam',
Subscription25__Billing_Country__c = 'Netherlands',
Subscription25__Bank__c = 'Test Bank',
Subscription25__BIC__c = 'test bic',
Subscription25__IBAN__c = 'test iban',
Subscription25__VAT_Number__c = '21',
Subscription25__Chamber_Of_Commerce_Number__c = 'test cocn',
Subscription25__Website__c = 'www.test.com'
);
admin.Name = entity;
admin.Subscription25__Invoice_Number_Prefix__c = '123';
Database.insert(admin);
Account acc = new Account();
acc.Name = 'Test Account';
acc.BillingStreet = 'Hoogte Kadijk 38-1';
acc.BillingPostalCode = '1018BM';
acc.BillingCity = 'Amsterdam';
acc.BillingCountry = 'Netherlands';
acc.ShippingStreet = 'Hoogte Kadijk 38-1';
acc.ShippingPostalCode = '1018BM';
acc.ShippingCity = 'Amsterdam';
acc.ShippingCountry = 'Netherlands';
Database.insert(acc);
Contact con = new Contact();
con.LastName = 'van Test';
con.FirstName = 'Testje';
con.Email = 'test@test.com';
Database.insert(con);
Product2 prod = new Product2();
prod.Name = 'Test Product';
Database.insert(prod);
PricebookEntry pbe = new PricebookEntry();
pbe.Pricebook2Id = Test.getStandardPricebookId();
pbe.Product2Id = prod.Id;
pbe.UnitPrice = 1;
Database.insert(pbe);
Subscription25__Invoice__c i = new Subscription25__Invoice__c();
i.Subscription25__Account__c = acc.Id;
i.Subscription25__Administration__c = admin.Id;
i.Subscription25__Invoice_Date__c = Date.today();
i.Subscription25__Description__c = 'invoice description';
i.Subscription25__Purchase_Order_Number__c = '1234';
i.Subscription25__Billing_Country__c = 'NLD';
Database.insert(i);
Subscription25__VATRate__c vatRate = new Subscription25__VATRate__c();
vatRate.Subscription25__Active__c = true;
vatRate.Subscription25__Default__c = true;
vatRate.Subscription25__VAT_Percentage__c = 19;
Database.insert(vatRate);
Subscription25__VAT_Code__c vatCode = new Subscription25__VAT_Code__c();
vatCode.Subscription25__Administration__c = admin.Id;
vatCode.Subscription25__VATRate__c = vatRate.Id;
vatCode.Subscription25__VAT_Code__c = 'VH';
Database.insert(vatCode);
Subscription25__Administration_Product__c ap = new Subscription25__Administration_Product__c();
ap.Subscription25__Administration__c = admin.id;
ap.Subscription25__Product__c = prod.Id;
ap.Subscription25__VATRate__c = vatRate.Id;
ap.Subscription25__Ledger_Account__c = '1000';
Database.insert(ap);
Subscription25__Debtor_Number__c dbNmbr = new Subscription25__Debtor_Number__c();
dbNmbr.Subscription25__Administration__c = admin.Id;
dbNmbr.Subscription25__Debtor_Number__c = '123';
dbNmbr.Subscription25__Account__c = acc.Id;
Database.insert(dbNmbr);
Order ord = new Order();
ord.AccountId = acc.Id;
ord.Subscription25__Administration__c = admin.Id;
ord.Pricebook2Id = Test.getStandardPricebookId();
ord.status = 'draft';
ord.Subscription25__Invoice__c = i.Id;
ord.EffectiveDate = Date.today();
Database.insert(ord);
OrderItem ordItem = new OrderItem();
ordItem.UnitPrice = 12;
ordItem.Quantity = 1;
ordItem.OrderId = ord.Id;
ordItem.Product2Id = prod.Id;
ordItem.Subscription25__Product__c = prod.Id;
ordItem.Subscription25__VAT_Code__c = 'VH';
ordItem.Subscription25__VAT_Percentage__c = 12;
Database.insert(ordItem);
}
@IsTest
static void test_constructor(){
PageReference pageRef = Page.Invoice; //name of visualforce page
Test.setCurrentPage(pageRef);
ApexPages.StandardController sc = new ApexPages.StandardController([
SELECT Id, Subscription25__Account__c,Subscription25__Administration__c,Subscription25__Invoice_Date__c,
Subscription25__Description__c,Subscription25__Purchase_Order_Number__c,Subscription25__Billing_Country__c
FROM Subscription25__Invoice__c
LIMIT 1
]);
Ctrl_InvoiceLayout ctrl = new Ctrl_InvoiceLayout(sc);
}
}
Visualforce Invoice
:
<apex:page standardController="Subscription25__Invoice__c" extensions="Ctrl_InvoiceLayout" renderAs="pdf" sidebar="false" showHeader="false" applyBodyTag="false">
<head>
<link media="print" rel="stylesheet" type="text/css" href="{!URLFOR($Resource.invoice, 'css/invoice.css')}" />
</head>
<body>
<div class="header">
<apex:image value="/servlet/servlet.FileDownload?file={!companyLogoId}" styleClass="logo"/>
</div>
<div class="header">
<div class="logoContainer">
<apex:image value="/servlet/servlet.FileDownload?file={!companyLogoId}" styleClass="logo" />
</div>
<table class="invoiceRulesHeader">
<thead>
<tr>
<th class="number">Quantity</th>
<th class="description">Description</th>
<th class="price right">Price</th>
<th class="total right">Total</th>
<th class="vat right">VAT</th>
</tr>
</thead>
</table>
</div>
<div class="footer">
<div class="container">
<ul>
<li>
<apex:outputField value="{!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__Billing_Name__c}" />
</li>
<li>
<apex:outputText value="{!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__Billing_Street__c}" />
</li>
<li>
<apex:outputText value="{!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__Billing_Postal_Code__c} {!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__Billing_City__c}" />
</li>
<li class="last">
<apex:outputText value="{!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__Billing_Country__c}" />
</li>
</ul>
<ul>
<li>
<apex:outputText value="VAT {!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__VAT_Number__c}" />
</li>
<li>
<apex:outputText value="Bank {!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__Bank__c}" />
</li>
<li>
<apex:outputText value="IBAN {!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__IBAN__c}" />
</li>
<li class="last">
<apex:outputText value="BIC_Swift {!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__BIC__c}" />
</li>
</ul>
</div>
<div class="pageNumbers">Page <span class="pagenumber"/> Of <span class="pagecount"/></div>
</div>
<div class="container">
<div class="invoicevat">
<apex:outputText value="INVOICE" />
</div>
<table class="contactDetails">
<tr>
<td class="contactName"><apex:outputText value="{!Subscription25__Invoice__c.Subscription25__Account__r.Name}" /></td>
</tr>
<apex:outputPanel layout="none" rendered="true">
<tr>
<td>
<apex:outputText value="Attn {!Subscription25__Invoice__c.Subscription25__Contact_Person__r.Name}" rendered="{!Subscription25__Invoice__c.Subscription25__Contact_Person__c != null}"/>
</td>
</tr>
</apex:outputPanel>
<tr>
<td><apex:outputText value="{!Subscription25__Invoice__c.Subscription25__Billing_Street__c}" /></td>
</tr>
<tr>
<td><apex:outputText value="{!Subscription25__Invoice__c.Subscription25__Billing_Postal_Code__c} {!Subscription25__Invoice__c.Subscription25__Billing_City__c}" /></td>
</tr>
<tr>
<td><apex:outputText value="{!Subscription25__Invoice__c.Subscription25__Billing_Country__c}" /></td>
</tr>
<tr>
<td><apex:outputText value="{!Subscription25__Invoice__c.Subscription25__VAT_Number__c}" /></td>
</tr>
</table>
<table class="invoiceDetails">
<tr>
<td class="leftColumn">InvoiceDate:</td>
<td><apex:outputField value="{!Subscription25__Invoice__c.Subscription25__Invoice_Date__c}" /></td>
</tr>
<tr>
<td>DueDate:</td>
<td><apex:outputField value="{!Subscription25__Invoice__c.Subscription25__Due_Date__c}" /></td>
</tr>
<tr>
<td>InvoiceNumber:</td>
<td><apex:outputField value="{!Subscription25__Invoice__c.Subscription25__Invoice_Number__c}" /></td>
</tr>
</table>
<apex:outputPanel rendered="{!NOT(ISBLANK(Subscription25__Invoice__c.Subscription25__Description__c))}" layout="none">
<div class="subject">
<apex:outputText value="Regarding: {!Subscription25__Invoice__c.Subscription25__Description__c}" />
</div>
</apex:outputPanel>
<table class="invoiceRules">
<thead>
<tr>
<th class="number">Quantity</th>
<th class="description">Description</th>
<th class="price right">Price</th>
<th class="total right">Total</th>
<th class="vat right">VAT</th>
</tr>
</thead>
<tbody>
<apex:repeat var="item" value="{!items}">
<tr>
<td class="number right">
<apex:outputText value="{0, number,###,###,##0.00}">
<apex:param value="{!item.quantity}" />
</apex:outputText>
</td>
<td class="description">
<div><apex:outputText value="{!item.accountLabel}" /></div>
<apex:outputText value="{!item.label}" />
<div class="subText"><apex:outputText value="{!item.description}" /></div>
</td>
<td class="price right">
<apex:outputText value="€{0, number,###,###,##0.00}">
<apex:param value="{!item.price}" />
</apex:outputText>
</td>
<td class="total right">
<apex:outputText value="€{0, number,###,###,##0.00}">
<apex:param value="{!item.total}" />
</apex:outputText>
</td>
<td class="vat right">
<apex:outputText value="{0, number,###,###,##0.00}%">
<apex:param value="{!item.tax}" />
</apex:outputText>
</td>
</tr>
</apex:repeat>
</tbody>
</table>
<div class="paymentDetails">
<table class="invoiceRulesFooter">
<tbody>
<tr>
<td class="number"></td>
<td colspan="2" class="totalText">Subtotal (ExclTax)</td>
<td class="total right">
<apex:outputText value="€{0, number,###,###,##0.00}">
<apex:param value="{!sum}" />
</apex:outputText>
</td>
<td class="vat"></td>
</tr>
<tr>
<td class="number"></td>
<td colspan="2" class="totalText">Tax</td>
<td class="total right">
<apex:outputText value="€{0, number,###,###,##0.00}">
<apex:param value="{!taxAmount}" />
</apex:outputText>
</td>
<td class="vat"></td>
</tr>
</tbody>
<tfoot>
<tr>
<td class="number"></td>
<td colspan="2" class="totalText">Total (InclTax)</td>
<td class="total right">
<apex:outputText value="€{0, number,###,###,##0.00}">
<apex:param value="{!total}" />
</apex:outputText>
</td>
<td class="vat"></td>
</tr>
</tfoot>
</table>
<apex:outputPanel styleClass="timeleyPayment" rendered="{!hasICP}">
ICPLawMessage
</apex:outputPanel>
<div class="timeleyPayment">
Please reference invoice number <strong>{!Subscription25__Invoice__c.Subscription25__Invoice_Number__c}</strong> with your payment. We appreciate your timely payment.
</div>
<table class="paymentInfo">
<tr>
<td class="first">Wire transfer to:</td>
<td></td>
</tr>
<tr>
<td>Bank Name:</td>
<td><apex:outputField value="{!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__Bank__c}" /></td>
</tr>
<tr>
<td>Bank Account Name:</td>
<td><apex:outputField value="{!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__Billing_Name__c}" /></td>
</tr>
<tr>
<td>BIC Swift:</td>
<td><apex:outputField value="{!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__BIC__c}" /></td>
</tr>
<tr>
<td>IBAN:</td>
<td><apex:outputField value="{!Subscription25__Invoice__c.Subscription25__Administration__r.Subscription25__IBAN__c}" /></td>
</tr>
</table>
<apex:outputPanel rendered="{!NOT(ISBLANK(Subscription25__Invoice__c.Subscription25__Foot_Note__c))}" layout="none">
<div class="via-email">
<apex:outputField value="{!Subscription25__Invoice__c.Subscription25__Foot_Note__c}" />
</div>
</apex:outputPanel>
</div>
</div>
</body>
<apex:outputText rendered="none" value="Subscription25__Invoice__c.Subscription25__Administration__c}" />
</apex:page>
Next up
Now you can setup Set up Automatic Invoice Creation and E-billing