Skip to main content

Salesforce.com Trigger : Generate Auto Running Number

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.

  1. Nomor urut yang perlu di-reset secara berkala, misal per bulan atau per tahun.
  2. Nomor urut yang unik untuk setiap data tertentu. Misal, nomor urut Account berdasar huruf awal.
  3. 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
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;
}
}
}
view raw gistfile1.java hosted with ❤ by GitHub


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
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
}
}
}
view raw gistfile1.cls hosted with ❤ by GitHub


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
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

Popular posts from this blog

PostGreSQL :: Hitung Umur

Ternyata untuk menghitung umur dari data yang disimpan di PostGreSQL sangat gampang. PostGreSQL sendiri sudah menyediakan fungsi yang mendukung. Beberapa fungsi yang bisa dipakai adalah AGE dan EXTRACT. AGE dipakai untuk menghitung umur dari sebuah data, dibandingkan dengan hari ini, atau dengan data lain (tipenya timestamp). Misal: age(timestamp '1980-09-27'), akan menghasilkan nilai (bertipe interval) "24 years 1 mon 25 days". Nah, kalau mau mengambil nilai tahunnya saja, tinggal menggunakan EXTRACT Contoh: EXTRACT(year FROM AGE(timestamp '2001-09-27')), hasilnya akan jadi 24.

PHP :: Selisih Jam

Setelah kemarin kesulitan untuk melakukan increment ataupun decrement terhadap variabel waktu (date/time) di PHP, kali ini nemuin masalah untuk mencari selisih waktu (dari satu jam ke jam tertentu), yang perlu untuk ngitung lembur karyawan. Tadinya kepikiran untuk buat fungsi sendiri, yang flow -nya kira-kira begini: - masing-masing dipisah menjadi jam, menit, detik - bandingkan antara keduanya. - lakukan pengurangan terhadap masing-masing komponen (jam, menit dan detik) - gabungkan hasil perhitungan ... (selisih jam + selisih menit + selisih detik) Tapi waktu aku ingat kasus Next Date, ... muncul ide untuk menggunakan cara yang sama, yaitu memanfaatkan format UNIX timestamp, terus nyoba mencari selisihnya. TERNYATA BERHASIL !!! Flownya seperti ini: - masing-masing dipisah menjadi jam, menit,detik - ubah masing-masing ke format timestamp, gunakan fungsi mktime() - kurangkan kedua jam - hasilnya dibagi 60 (karena satuannya pakai menit), sementara selisih timestamp itu dalam detik Algori...

Delphi :: Split String

Akhirnya ketemu juga cara untuk melakukan split string. (Terbiasa pakai PHP sih, yang sangat memanjakan dalam pengelolaan string.) Di sini memanfaatkan TStringList, unit Classes. // procedure untuk split string procedure Split (const Delimiter: Char; // delimiter charachter Input: string; // input string const Strings: TStrings) ; // list of string result begin Assert(Assigned(Strings)) ; Strings.Clear; Strings.Delimiter := Delimiter; Strings.DelimitedText := Input; end; // contoh pemakaian procedure TForm1.Button1Click(Sender: TObject) ; var A: TStringList; begin A := TStringList.Create; try Split(' ', 'your delphi guide', A) ; ShowMessage(a[0]) ; //your ShowMessage(a[1]) ; //delphi ShowMessage(a[2]) ; //guide finally A.Free; end; end; Source : http://delphi.about.com/cs/adptips2002/a/bltip1102_5.htm Wheew .. akhirnya. One step ahead!!