Salesforce memiliki tipe data berupa autonumber, yang akan terisi otomatis dengan nilai yang selalu bertambah (auto increment). Ternyata ini tidak hanya untuk field Name, tapi kita juga bisa menambah baru. Kita juga bisa menentukan berapa jumlah karakter dari nilai autonumber yang kita inginkan. Misalnya kita ingin field itu berisi 5 karakter, maka kita cukup atur {00000} dan nilainya nanti akan menjadi 00001, 00002 dst. Kita bahkan bisa menggabungkan dengan huruf tertentu, misal X-{00}, akan menjadi X-01 dst. Atau bahkan dengan tahun, bulan dan tanggal record tersebut dibuat, misal {YYYY}/{MM}/{000} akan menghasilkan 2012/11/001 dst.
Meskipun demikian, ada beberapa kekurangan yang belum aku temukan solusinya selain dengan membuat trigger untuk menghasilkan nilai nomor urut (running number) tersebut. Berikut beberapa kasus yang sepertinya tidak bisa dihandle hanya dengan field auto number tersebut.
Sebenarnya untuk setiap kasus manapun, algoritma dasarnya adalah standard.
Sebagai contoh, kita ingin menghasilkan nomor Invoice sendiri, tanpa menggunakan field autonumber yang standard. Object Invoice memiliki field sebagai berikut:
* Name - Text(80)
* Account__c - Lookup (Account)
* Invoice_Date__c - Date
* Invoice_Type__c - Picklist(A, B, C)
* Month_Code__c - Formula (Year-Month of Invoice Date)
* Acc_Running_No__c - Number(10,0)
Field Name akan kita gunakan sebagai nomor Invoice, dan kita tidak ingin menggunakan standard autonumber, melainkan dengan Text. Tentu saja kita tidak mau user menginput sendiri nomor invoice karena mereka akan mengeluh ketika harus mengelola nomor urut sendiri. Jadi kita bantu dengan mengisi secara otomatis. Di sini kita batasi, bahwa nomor invoice akan digerenate khusus untuk record baru (insert) saja.
Masih banyak kasus lain tentunya, tapi setidaknya contoh-contoh sederhana di atas bisa memberi ide bagaimana membuat nomor sendiri.
Meskipun demikian, ada beberapa kekurangan yang belum aku temukan solusinya selain dengan membuat trigger untuk menghasilkan nilai nomor urut (running number) tersebut. Berikut beberapa kasus yang sepertinya tidak bisa dihandle hanya dengan field auto number tersebut.
- Nomor urut yang perlu di-reset secara berkala, misal per bulan atau per tahun.
- Nomor urut yang unik untuk setiap data tertentu. Misal, nomor urut Account berdasar huruf awal.
- Nomor urut yang unik untuk setiap data terkait (lookup). Misal, nomor urut Invoice tiap Account punya urutan tersendiri.
Sebenarnya untuk setiap kasus manapun, algoritma dasarnya adalah standard.
- Cari nomor urut terakhir
- Increment angka
- Format angka
- Simpan data
Sebagai contoh, kita ingin menghasilkan nomor Invoice sendiri, tanpa menggunakan field autonumber yang standard. Object Invoice memiliki field sebagai berikut:
* Name - Text(80)
* Account__c - Lookup (Account)
* Invoice_Date__c - Date
* Invoice_Type__c - Picklist(A, B, C)
* Month_Code__c - Formula (Year-Month of Invoice Date)
* Acc_Running_No__c - Number(10,0)
Field Name akan kita gunakan sebagai nomor Invoice, dan kita tidak ingin menggunakan standard autonumber, melainkan dengan Text. Tentu saja kita tidak mau user menginput sendiri nomor invoice karena mereka akan mengeluh ketika harus mengelola nomor urut sendiri. Jadi kita bantu dengan mengisi secara otomatis. Di sini kita batasi, bahwa nomor invoice akan digerenate khusus untuk record baru (insert) saja.
Kasus 1 : Nomor Invoice reset setiap tahun, berdasar Created Date
File: https://gist.github.com/4162169
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trigger InvoiceTrigger on Invoice__c (before insert) { | |
if (trigger.isBefore && trigger.isInsert) | |
{ | |
// ---- GENERATE INVOICE NUMBER, AUTO RUNNING NUMBER, RESET EVERY YEAR BASED ON CREATED DATE --- // | |
// ---- INVOICE NUMBER FORMAT : {0000} --// | |
Integer iCharLen = 4; // character length for number | |
Integer iLastNo = 0; // information last running number this year | |
Integer iThisYear = Date.Today().year(); | |
// search latest invoice number | |
String strTemp = '0'; | |
String strZero = '0'; | |
List<Invoice__c> lsInvoice = new List<Invoice__c>([SELECT Id, Name FROM Invoice__c WHERE CALENDAR_YEAR(CreatedDate) = :iThisYear LIMIT 1 ]); | |
if (lsInvoice.size() > 0) strTemp = lsInvoice[0].Name; // in this case, invoice number only contains numeric value : 001, 003, etc | |
iLastNo = Integer.valueOf(strTemp); | |
// start generate number | |
for(Invoice__c Inv: trigger.new) | |
{ | |
iLastNo++; | |
strTemp = String.valueOf(iLastNo); | |
if (strTemp.length() < iCharLen) strTemp = strZero.repeat(iCharLen - strTemp.length()) + strTemp; // add 0 prefix | |
Inv.Name = strTemp; | |
} | |
} | |
} |
Kasus 2 : Nomor Invoice reset setiap bulan, berdasar Invoice Date
Di sini kita akan menggunakan Month_Code__c untuk memudahkan query.
File: https://gist.github.com/4176831
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trigger InvoiceTrigger2 on Invoice__c (before insert) { | |
if (trigger.isBefore && trigger.isInsert) | |
{ | |
// ---- GENERATE INVOICE NUMBER, AUTO RUNNING NUMBER, RESET EVERY MONTH BASED ON INVOICE DATE --- // | |
// ---- INVOICE NUMBER FORMAT : {YYYY}-{MM}-{000} --// | |
// ---- ASSUME THAT INVOICE DATE CAN BE BACKDATED -- // | |
Integer iCharLen = 3; // character length for number | |
Integer iLastNo = 0; // information last running number this year | |
String strZero = '0'; | |
// search type of invoice month, because possible to have trigger for different month | |
String strMonthCode = ''; | |
String strTemp; | |
Map<String, Integer> mapMonthLast = new Map<String, Integer>(); // map of last number for a month | |
for (Invoice__c Inv: trigger.new) | |
{ | |
strMonthCode = String.ValueOf(Inv.Invoice_Date__c.year()) + '-' + String.ValueOf(Inv.Invoice_Date__c.month()); | |
mapMonthLast.put(strMonthCode, 0); // add default last number | |
} | |
// search latest invoice number | |
List<String> arrStr = new List<String>(); | |
Set<String> setMonthCode = mapMonthLast.keyset(); | |
for (AggregateResult ar : [Select CALENDAR_YEAR(Invoice_Date__c) inv_year, CALENDAR_MONTH(Invoice_Date__c) inv_month, | |
MAX(Name) last_name | |
From Invoice__c | |
WHERE IsDeleted = false AND Month_Code__c IN : setMonthCode | |
GROUP BY CALENDAR_YEAR(Invoice_Date__c), CALENDAR_MONTH(Invoice_Date__c) | |
]) | |
{ | |
strTemp = String.valueOf(ar.get('last_name')); | |
arrStr = strTemp.split('-'); | |
strMonthCode = String.valueOf(ar.get('inv_year')) + '-' + String.valueOf(ar.get('inv_month')); | |
if (arrStr.size() > 2 && arrStr[2].isNumeric()) mapMonthLast.put(strMonthCode, Integer.valueOf(arrStr[2])); | |
} | |
// start generate number | |
String strNo = ''; | |
for(Invoice__c Inv: trigger.new) | |
{ | |
iLastNo = 0; // init | |
strMonthCode = String.ValueOf(Inv.Invoice_Date__c.year()) + '-' + String.ValueOf(Inv.Invoice_Date__c.month()); | |
if (mapMonthLast.containsKey(strMonthCode)) iLastNo = mapMonthLast.get(strMonthCode); | |
iLastNo++; // update to next number | |
strTemp = String.valueOf(iLastNo); | |
if (strTemp.length() < iCharLen) strTemp = strZero.repeat(iCharLen - strTemp.length()) + strTemp; // add 0 prefix | |
strNo = String.ValueOf(Inv.Invoice_Date__c.year()) + '-'; | |
strNo += (Inv.Invoice_Date__c.month() < 10) ? '0' + String.ValueOf(Inv.Invoice_Date__c.month()) : String.ValueOf(Inv.Invoice_Date__c.month()); | |
strNo += '-' + strTemp; | |
Inv.Name = strNo; // update field | |
mapMonthLast.put(strMonthCode, iLastNo); // update map info | |
} | |
} | |
} |
Kasus 3 : Nomor Invoice setiap customer/account memiliki urutan sendiri
Nah di sini, aku merasa perlu menggunakan field Acc_Running_No__c untuk menyimpan nomor terakhir, karena agak sulit saat melakukan query dalam menentukan nomor terbesar File: https://gist.github.com/4203741
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trigger InvoiceTrigger3 on Invoice__c (before insert) { | |
if (trigger.isBefore && trigger.isInsert) | |
{ | |
// ---- GENERATE INVOICE NUMBER, AUTO RUNNING NUMBER, EACH ACCOUNT HAS ITS OWN RUNNING NUMBER --- // | |
// ---- INVOICE NUMBER FORMAT : {ACCOUNTNUMBER}-{YYYY}-{MM}-{000} --// | |
// ---- ASSUME THAT ACCOUNT NUMBER IS MANDATORY -- // | |
// ---- BECAUSE WE HAVE YEAR AND MONTH INFORMATION IN THE NUMBER, EASIER IF WE CREATE A FIELD FOR THE NUMBER [ACC RUNNING NO] --// | |
Integer iCharLen = 3; // character length for number | |
Integer iLastNo = 0; // information last running number this year | |
String strZero = '0'; | |
// search list of account | |
String strTemp; | |
Map<Id, Integer> mapAccLast = new Map<Id, Integer>(); // map of last number for an account | |
for (Invoice__c Inv: trigger.new) | |
{ | |
mapAccLast.put(Inv.Account__c, 0); // add default last number | |
} | |
// search latest invoice number | |
List<String> arrStr = new List<String>(); | |
Set<Id> setAccId = mapAccLast.keyset(); | |
Map<Id, String> mapAccNumber = new Map<Id, String>();// map account number for each account id | |
for (AggregateResult ar : [Select Account__c, MAX(Acc_Running_No__c) last_no | |
From Invoice__c | |
WHERE IsDeleted = false AND Account__c IN : setAccId | |
GROUP BY Account__c | |
]) | |
{ | |
strTemp = String.valueOf(ar.get('last_no')); | |
mapAccLast.put(String.valueOf(ar.get('Account__c')), Integer.valueOf(strTemp)); | |
} | |
// get information of account number | |
for (Account A: [SELECT Id, AccountNumber FROM Account WHERE Id IN : setAccId]) | |
{ | |
mapAccNumber.put(A.Id, A.AccountNumber); | |
} | |
// start generate number | |
String strNo = ''; | |
for(Invoice__c Inv: trigger.new) | |
{ | |
iLastNo = 0; // init | |
if (mapAccLast.containsKey(Inv.Account__c)) iLastNo = mapAccLast.get(Inv.Account__c); | |
iLastNo++; // update to next number | |
strTemp = String.valueOf(iLastNo); | |
if (strTemp.length() < iCharLen) strTemp = strZero.repeat(iCharLen - strTemp.length()) + strTemp; // add 0 prefix | |
strNo = ''; | |
//if (mapAccNumber.containsKey(Inv.Account__c)) | |
strNo += mapAccNumber.get(Inv.Account__c) + '-'; | |
strNo += String.ValueOf(Inv.Invoice_Date__c.year()) + '-'; | |
strNo += (Inv.Invoice_Date__c.month() < 10) ? '0' + String.ValueOf(Inv.Invoice_Date__c.month()) : String.ValueOf(Inv.Invoice_Date__c.month()); | |
strNo += '-' + strTemp; | |
Inv.Name = strNo; // update field | |
Inv.Acc_Running_No__c = iLastNo; | |
mapAccLast.put(Inv.Account__c, iLastNo); // update map info | |
} | |
} | |
} |
Masih banyak kasus lain tentunya, tapi setidaknya contoh-contoh sederhana di atas bisa memberi ide bagaimana membuat nomor sendiri.
Comments