Belajar Merancang Laporan JasperReports Dengan iReport
25 Agustus 2013 9 Komentar
iReport membuat perancangan layout laporan JasperReports menjadi mudah. Sebagai latihan, saya akan belajar membuat sebuah laporan penjualan yang dikelompokkan berdasarkan nama sales. Pengguna dapat memilih untuk melihat laporan berdasarkan periode tertentu (tanggal mulai dan tanggal selesai). Karena saya memakai JPA, maka saya akan melewatkan kumpulan object (entitas) yang telah di-query oleh JPA ke laporan. Dengan demikian, laporan tidak akan mengerjakan SQL secara langsung. Lalu, saya harus mulai dari mana?
Agar dapat melihat preview laporan di iReport, saya perlu membuat sebuah method sederhana yang akan mengembalikan contoh object yang bisa dipakai untuk preview nantinya. Sebagai contoh, saya menambahkan method berikut ini di class bernama ReportTest.groovy:
static List getPenjualanPerSales() { Sales sales1 = new Sales(nama: 'Solid Snake') Sales sales2 = new Sales(nama: 'Liquid Snake') Sales sales3 = new Sales(nama: 'Big Boss') DateTimeFormatter format = DateTimeFormat.forPattern('dd-MM-yyyy') Barang barang1 = new Barang(kode: "BRG1") Barang barang2 = new Barang(kode: "BRG2") FakturJual jual1 = new FakturJual(nomor: "FJ-001", tanggal: LocalDate.parse('01-07-2013', format), sales: sales1) jual1.tambahItem(barang1, 10000, 10) jual1.tambahItem(barang2, 20000, 20) FakturJual jual2 = new FakturJual(nomor: "FJ-002", tanggal: LocalDate.parse('15-07-2013', format), sales: sales1) jual2.tambahItem(barang1, 10000, 30) jual2.tambahItem(barang2, 20000, 40) FakturJual jual3 = new FakturJual(nomor: "FJ-003", tanggal: LocalDate.parse('25-07-2013', format), sales: sales2) jual3.tambahItem(barang1, 10000, 50) jual3.tambahItem(barang2, 20000, 60) FakturJual jual4 = new FakturJual(nomor: "FJ-004", tanggal: LocalDate.parse('01-08-2013', format), sales: sales3) jual4.tambahItem(barang1, 10000, 70) jual4.tambahItem(barang2, 20000, 80) // Penjualan tanpa sales FakturJual jual5 = new FakturJual(nomor: "FJ-005", tanggal: LocalDate.parse('15-08-2013', format)) jual5.tambahItem(barang1, 10000, 10) jual5.tambahItem(barang2, 20000, 20) [jual1, jual2, jual3, jual4, jual5] }
Perhatikan bahwa method tersebut harus berupa method static. Object yang dikembalikan tidak perlu diambil dari database; mereka hanya akan dipakai untuk men-preview laporan selama berada di iReport nanti. Saya perlu membuat JAR dari kode program di atas lalu menambahkannya di iReport dengan memilih menu Tools, Options, ClassPath dan men-klik tombol Add JAR. Lalu, saya men-klik tombol Report Datasources dan men-klik tombol New. Pada kotak dialog yang muncul, saya memilih JavaBeans set data source dan men-klik tombol Next. Saya kemudian mengisi kotak dialog berikutnya seperti pada gambar berikut ini:
Untuk memastikan tidak ada yang salah, saya men-klik tombol Test. Setelah selesai, saya men-klik tombol Save dan menutup dialog dengan men-klik tombol Close.
Selanjutnya, saya akan membuat laporan baru, dengan memilih menu File, New…, Blank A4 dan men-klik Open this Template. Pada kotak dialog yang muncul, saya kemudian mengisi nama laporan dan lokasi penyimpan laporan. Setelah itu, saya men-klik tombol Next dan Finish.
Saya kemudian men-klik tombol Report Query dan memilih tab JavaBean Datasource. Saya mengisi seperti dengan yang terlihat pada gambar berikut ini sebelum men-klik tombol OK:
Agar dapat memanggil method pada object Barang
, saya perlu menambahkan sebuah field baru dengan nama _THIS
. Ini adalah nama khusus untuk field dimana nilainya akan mewakili object yang sedang diproses saat ini (bayangkan keyword this
di Java!). Saya mulai dengan men-klik kanan Fields dan memilih Add Field. Setelah itu saya mengisi properties-nya seperti pada gambar berikut ini:
Laporan perlu dikelompokkan berdasarkan nama sales sehingga saya dapat menampilkan subtotal untuk setiap sales nantinya. Oleh sebab itu, saya men-klik kanan nama laporan, dan memilih Add Report Group seperti yang terlihat pada gambar berikut ini:
Saya mengisi dialog yang muncul seperti pada gambar berikut ini:
Saya kemudian men-klik tombol Next. Pada halaman ini, saya menghilangkan tanda centang pada Add the group header. Setelah itu, saya men-klik tombol Finish.
Sekarang saya dapat membuat judul laporan yang harus ada di setiap halaman. Pada judul halaman ini, akan terdapat informasi periode laporan. Karena nilai periode laporan adalah sesuatu yang tidak statis, maka saya perlu membuat parameter baru. Caranya adalah dengan memilih Parameters di windows Report Inspector dan memilih menu Add Parameter. Kemudian saya mengisi properties seperti yang terlihat pada gambar berikut ini:
Berikut ini adalah contoh rancangan pada band Page Header yang saya buat:
Untuk memberikan informasi halaman, saya menggunakan ekspresi Groovy berikut ini:
"Hal ${$V{PAGE_NUMBER}}"
Prefix $V
adalah cara di JasperReports untuk mengakses sebuah variabel. PAGE_NUMBER adalah nama sebuah variabel bawaan yang nilainya berupa nomor halaman saat ini.
Untuk menampilkan informasi periode, saya menggunakan ekspresi Groovy berikut ini:
"Periode: ${$P{tanggalMulaiCari}.toString('dd-MM-yyyy')} s/d ${$P{tanggalSelesaiCari}.toString('dd-MM-yyyy')}"
Prefix $P
di JasperReports dipakai untuk mengakses nilai dari parameter. Berbeda dengan variabel, parameter adalah nilai yang dilewatkan oleh pengguna saat menampilkan laporan. Nilai dari sebuah parameter tidak akan bisa diketahui bila laporan belum di-eksekusi.
Berikutnya, saya mengisi band Column Header dan Detail 1 sehingga terlihat seperti pada gambar berikut ini:
Untuk mengakses nilai field di JasperReports, saya dapat menggunakan prefix $F
. Bila data source berupa JavaBean, maka $F
akan mengembalikan nilai property dari object (sebuah object mewakili sebuah record/baris). Sebagai contoh, $F{nomor}
akan mengembalikan nilai dari getNomor()
untuk object bersangkutan.
Saya memakai ekspresi Groovy berikut ini untuk menampilkan kolom sales:
$F{sales}? $F{sales}.nama: "(tanpa sales)"
Nilai property sales
bisa saja berupa null
untuk penjualan secara langsung. Ekspresi di atas akan mengembalikan nama sales bila terdapat nilai sales; bila nilai sales adalah null
, maka ia akan mengembalikan tulisan ‘(tanpa sales)’.
Saya memakai ekspresi Groovy berikut ini untuk menampilkan tanggal:
$F{tanggal}.toString('dd-MM-yyyy')
Karena property tanggal
di class FakturJual
bertipe org.joda.time.LocalDate
, maka saya dapat memanggil method toString()
(bawaan Joda Time) untuk men-format tanggal.
Class FakturJual
memiliki sebuah method bernama total()
yang akan mengembalikan nilai transaksi untuk faktur tersebut. Untuk memanggil method dari object yang sedang diproses, saya dapat memakai $F{_THIS}
. Ekspresi ini mirip seperti keyword this
di Java yang akan mengembalikan object pada konteks yang sedang aktif. Dengan demikian, untuk memanggil method total()
, saya dapat menggunakan ekspresi berikut ini:
$F{_THIS}.total()
Kolom total berisi angka dengan tipe BigDecimal
. Alangkah baiknya bila angka ini diformat, misalnya dengan pemisah ribuan berupa titik. Untuk itu, saya men-klik kanan pada field total, kemudian memilih Field Pattern. Pada dialog yang muncul, saya memilih Number, mengisi Decimal places dengan 0, dan memberikan tanda centang pada Use 1000 separator. Setelah itu, saya men-klik tombol Apply.
Tujuan awal saya melakukan grouping berdasarkan sales adalah agar dapat menampilkan subtotal untuk masing-masing sales. Untuk menghitung subtotal per sales, saya perlu menambahkan variables baru. Saya men-klik kanan pada Variables, kemudian memilih Add Variables. Gambar berikut ini memperlihatkan variables yang saya tambahkan:
Setelah itu saya menambahkan rancangan untuk band sales Group Footer 1 seperti yang terlihat pada gambar berikut ini:
Band Group Footer 1 tersebut hanya memiliki dua TextField. Yang pertama berisi label keterangan, dan yang satunya lagi menampilkan isi variabel $V{subtotal}
. Khusus untuk TextField yang berisi label keterangan, saya memakai ekspresi seperti berikut ini:
$F{sales}?"Subtotal Untuk Sales ${$F{sales}.nama}: ": "Subtotal Untuk Penjualan Tanpa Sales: "
Terakhir, saya akan menampilkan nilai grand total penjualan. Untuk itu saya kembali membuat sebuah Variables baru seperti yang ditunjukkan pada gambar berikut ini:
Setelah itu, saya menampilkan rancangan berikut ini pada band Summary:
Bila saya men-preview laporan, saya akan memperoleh tampilan seperti berikut ini:
Perhatikan bahwa untuk setiap group, nama sales yang sama akan tetap akan dicetak di kolom sales. Bila hal ini dirasa menganggu, saya dapat menghilangkan nama sales yang sama dengan memilih field tersebut, kemudian pada window Properties, saya menghilangkan tanda centang pada Print Repeated Values. Sekarang hasil preview laporan akan terlihat seperti pada gambar berikut ini:
Sekarang saya hanya perlu menampilkan laporan tersebut di aplikasi. Bila seandainya saya memakai Griffon dan simple-jpa, maka cara terbaik adalah meletakkan query sebagai JPA named query. Sebagai contoh, pada class FakturJual
, saya menambahkan named query berikut ini:
@DomainModel @Entity @Canonical @NamedQuery(name="FakturJual.RekapPenjualanSales", query=''' FROM FakturJual f WHERE (f.tanggal BETWEEN :tanggalMulaiCari AND :tanggalSelesaiCari) AND (f.hapus <> TRUE) AND TYPE(f) IS FakturJual ORDER BY f.sales, f.tanggal ''') class FakturJual { ... }
Untuk mengerjakan di atas melalui simple-jpa, saya perlu memanggil method dengan nama doRekapPenjualanSalesOnFakturJual
dan melewatkan sebuah Map
yang berisi nilai parameter tanggalMulaiCari
dan tanggalSelesaiCari
.
Karena biasanya jenis laporan ditampilkan melalui sebuah ComboBox
yang dapat dipilih, maka saya dapat membuat enumeration
seperti berikut ini:
enum JenisLaporan { REKAP_PEMBELIAN("Rekap Pembelian", "laporan_rekap_pembelian", "FakturBeli", "RekapPembelian"), REKAP_PENJUALAN("Rekap Penjualan", "laporan_rekap_penjualan", "FakturJual", "RekapPenjualan"), REKAP_PENJUALAN_SALES("Rekap Penjualan Per Sales", "laporan_rekap_penjualan_sales", "FakturJual", "RekapPenjualanSales"), String keterangan String namaLaporan String domainClass String reportMethod public JenisLaporan(String keterangan, String namaLaporan, String domainClass, String reportMethod) { this.keterangan = keterangan this.namaLaporan = namaLaporan this.domainClass = domainClass this.reportMethod = reportMethod } @Override String toString() { keterangan } }
SwingX memiliki EnumComboBoxModel
yang memungkinkan untuk mengisi sebuah ComboBox
berdasarkan elemen yang ada di sebuah Enumeration
:
class ReportModel { @Bindable LocalDate tanggalMulaiCari @Bindable LocalDate tanggalSelesaiCari EnumComboBoxModel<JenisLaporan> jenisLaporanSearch = new EnumComboBoxModel<JenisLaporan>(JenisLaporan.class) }
Pada controller, saya dapat membuat sebuah method universal yang akan menampilkan laporan berdasarkan elemen yang terpilih di ComboBox
, misalnya seperti berikut ini:
def search = { JenisLaporan jenisLaporan = model.jenisLaporanSearch.selectedItem Map parameter = ['tanggalMulaiCari': model.tanggalMulaiCari, 'tanggalSelesaiCari': model.tanggalSelesaiCari] List source = "do${jenisLaporan.reportMethod}On${jenisLaporan.domainClass}"(parameter) JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(source) JasperPrint jasperPrint = JasperFillManager.fillReport( getResourceAsStream("report/${jenisLaporan.namaLaporan}.jasper"), parameter, dataSource) execInsideUIAsync { view.content.clear() view.content.add(new JRViewer(jasperPrint), BorderLayout.CENTER) } }
Pada kode program di atas, saya memakai fasilitas dinamis dari Groovy untuk memanggil method simple-jpa berdasarkan String
yang dibentuk dari JenisLaporan
terpilih. Misalnya, bila user memilih REKAP_PENJUALAN
, maka method simple-jpa yang dikerjakan adalah doRekapPenjualanOnFakturJual()
; bila user memilih REKAP_PEMBELIAN
, maka method simple-jpa yang dikerjakan adalah doRekapPembelianOnFakturBeli()
; dan sebagainya. Dengan cara seperti ini, bila saya ingin menambahkan sebuah laporan baru, maka saya hanya perlu menambahkan sebuah elemen baru di enumeration JenisLaporan
tanpa mengubah kode program di controller dan view.