DdDesignReport

Integrasi dalam 10 menit

Panduan memasang DdDesignReport sebagai fitur di aplikasi Anda: pelanggan mengubah sendiri tata letak faktur, struk, atau slip gajinya, dan aplikasi mencetaknya dengan data dari database Anda.

Contoh lengkapnya, yang bisa langsung dijalankan, ada di examples/app: satu server Node.js (±120 baris, tanpa framework) dan satu halaman.

cd examples/app
npm install
node server.js          # buka http://localhost:3000

Gambaran

Ada empat bagian, dan hanya dua yang perlu Anda tulis sendiri:

 browser                               server Anda                          ddreport
┌─────────────────────┐  simpan     ┌──────────────────────────┐
│ designer            │ ──────────► │ tabel template:          │
│ (ddreport-designer) │  JSON       │ (pelanggan, laporan) →   │
│                     │             │ JSON template            │
│ tombol Preview      │ ──────────► │                          │  template + data   ┌──────────┐
│ tombol Cetak / PDF  │ ──────────► │ query data laporan  ─────┼──────────────────► │ render   │
└─────────────────────┘ ◄────────── │                          │ ◄──────────────────│ PDF, ... │
                         dokumen    └──────────────────────────┘     dokumen        └──────────┘
  1. Designer di halaman: satu createDesigner(...).
  2. Penyimpanan template: satu baris per pelanggan dan laporan. Ini milik Anda.
  3. Data laporan: query yang sudah Anda punya, dibentuk seperti yang dibaca template. Ini juga milik Anda.
  4. Render: satu panggilan, dari Node.js, Java, Kotlin, Go, atau HTTP untuk PHP, Python, dan .NET.

Template disimpan di server Anda dan dikirim ke ddreport bersama datanya setiap kali dicetak; render sendiri tidak menyimpan apa pun.

1. Designer di halaman

npm install ddreport-designer
import { createDesigner } from 'ddreport-designer'

const designer = createDesigner(document.getElementById('designer'), {
  lang: 'id',
  simple: true,                       // untuk admin dan staf: alat yang perlu saja, setiap tombol bernama
  preview: async template =>          // tombol Preview: server Anda mencetaknya sebagai HTML
    (await fetch('/api/preview/invoice', { method: 'POST', body: JSON.stringify(template) })).text(),
})

const { template, data } = await (await fetch('/api/templates/invoice')).json()
designer.setTemplate(template)
designer.setData(data)               // contoh data: chart di kanvas, menu Field, dan peringatan field yang salah ketik

Tanpa bundler, sajikan node_modules/ddreport-designer/designer.js sebagai file statis lalu import { createDesigner } from '/designer.js', seperti di contoh. Untuk React dan Vue: README paket designer.

Di Laravel atau framework lain tanpa npm: unduh https://cdn.jsdelivr.net/npm/ddreport-designer@0.4.3/designer.js ke folder public/, lalu import { createDesigner } from '/designer.js'. Satu file, tanpa dependency.

2. Menyimpan template per pelanggan

Satu tabel cukup:

CREATE TABLE report_templates (
  customer_id BIGINT       NOT NULL,
  report      VARCHAR(50)  NOT NULL,   -- 'invoice', 'receipt', ...
  template    JSON         NOT NULL,
  updated_at  TIMESTAMP    NOT NULL,
  PRIMARY KEY (customer_id, report)
);

Selama pelanggan belum menyimpan tata letaknya sendiri, pakai template siap pakai (ddreport-designer/templates/invoice.json, atau buatan Anda). Saat menyimpan, coba render dulu dengan datanya, agar template yang tidak bisa dicetak ditolak dengan pesan yang jelas:

await renderer.render(template, data, 'html') // melempar error dengan penjelasan bila template rusak
db.save(customerId, 'invoice', template)

customerId selalu diambil dari sesi login di server, bukan dari permintaan browser, agar pelanggan tidak bisa membuka template pelanggan lain.

3. Bentuk data

Template membaca satu objek: field biasa untuk kop dan total, ditambah daftar rows yang diulang di band detail.

{
  "number": "INV-0042",
  "customer": { "name": "Toko Maju", "city": "Sleman" },
  "items": [ { "name": "Kopi", "qty": 2, "price": 18000 } ],
  "rows":  [ { "name": "Kopi", "qty": 2, "price": 18000 } ]
}
Di templateDibaca dari
{{number}}, {{customer.city}}field dan field bersarang
band detailsetiap item rows (nama lain: "dataKey")
tabel atau sub-laporan dengan Data itemssetiap item daftar items
{{sum(qty * price)}}, {{count()}}dihitung dari baris yang sedang dicetak

Faktur biasanya dua query: kepala faktur menjadi field, baris barangnya menjadi rows. Angka yang dikirim database sebagai teks ("18000.00") tetap dihitung sebagai angka.

Field yang salah ketik

Placeholder yang field-nya tidak ada di data tercetak kosong, tanpa error. Ada tiga cara menangkapnya:

4. Render

Node.js (npm install ddreport, binary untuk sistemnya ikut terpasang):

const { createRenderer } = require('ddreport')
const renderer = createRenderer()   // satu proses yang terus berjalan

app.get('/reports/invoice/:id.pdf', async (req, res) => {
  const template = await db.template(req.user.customerId, 'invoice')
  const data = await invoiceData(req.params.id)          // query Anda
  res.type('pdf').send(await renderer.render(template, data, 'pdf'))
})

Format lain: 'xlsx', 'xls', 'csv', 'html', 'escpos' (printer struk), 'txt'.

Laravel, lewat server ddreport (Docker) di mesin yang sama atau lain:

docker run -d -p 8080:8080 -e DDREPORT_TOKEN=rahasia-panjang ghcr.io/gvjavx/ddreport
// routes/web.php
Route::get('/reports/invoice/{invoice}.pdf', function (Invoice $invoice) {
    $template = ReportTemplate::for(auth()->user()->customer_id, 'invoice');   // JSON tersimpan
    $data = [
        'number'   => $invoice->number,
        'customer' => ['name' => $invoice->customer->name],
        'rows'     => $invoice->items->map->only(['name', 'qty', 'price']),
    ];
    $pdf = Http::withToken(env('DDREPORT_TOKEN'))
        ->post('http://localhost:8080/render?format=pdf', ['template' => json_decode($template), 'data' => $data])
        ->throw()->body();
    return response($pdf)->header('Content-Type', 'application/pdf');
});

// Preview dari designer: template yang sedang diubah, dengan data sungguhan
Route::post('/api/preview/invoice', fn (Request $r) => Http::withToken(env('DDREPORT_TOKEN'))
    ->post('http://localhost:8080/render?format=html', ['template' => $r->json()->all(), 'data' => /* data contoh */ []])
    ->throw()->body());

Java dan Kotlin: DdReport.render(templateJson, dataJson, "pdf"), atau new DdReport.Renderer() di server. Go: ddreport.Render(template, data, "pdf"). Selengkapnya di README.

5. Cetak ke printer struk

escpos menghasilkan perintah printer, bukan gambar. Browser tidak bisa mengirimnya ke printer begitu saja; pilih jalur yang cocok dengan printernya:

PrinterJalur
Printer jaringan (LAN/Wi-Fi), hampir semua printer kasir punya port 9100Server mengirim byte-nya ke ip-printer:9100. Contoh: route /api/print di examples/app/server.js (net.connect). Di PHP: fsockopen($ip, 9100) lalu fwrite.
Bluetooth, atau USB yang muncul sebagai port serialBrowser memakai Web Serial (Chrome dan Edge di komputer). Contoh: tombol Cetak struk (USB/Bluetooth) di examples/app/index.html.
USB yang terpasang sebagai printer WindowsBagikan printernya (Sharing), lalu dari komputer kasir: copy /b struk.escpos \\localhost\NamaPrinter, atau pakai agen cetak lokal. Browser tidak bisa menjangkaunya langsung.
Android (Flutter, Kotlin)Kirim byte escpos dari server ke aplikasi, lalu tulis ke printer lewat Bluetooth seperti perintah ESC/POS lainnya.

Lebar baris mengikuti lebar kertas di template (Struk 58 mm = 32 karakter, 80 mm = 48); ubah dengan Karakter per baris bila printer Anda berbeda.

Masalah yang sering muncul

GejalaSebab dan jalan keluar
Field tercetak kosongNama field salah ketik atau tidak ada di hasil query. Lihat peringatan di Preview, atau jalankan dengan DDREPORT_STRICT=1.
Node.js: no ddreport binary for linux-x64Paket binary untuk sistem itu tidak ikut terpasang: npm install dijalankan dengan --omit=optional, atau node_modules disalin dari sistem lain (Windows ke server Linux, atau ke image Docker). Jalankan npm install di sistem tujuan, atau set DDREPORT_BIN.
Java: Add the dependency io.github.gvjavx:ddreport with the classifier of this systemTambahkan dependency kedua dengan classifier sistem server (linux-x64), bukan laptop Anda. Untuk beberapa sistem, tambahkan beberapa classifier. Nama package Java-nya io.ddreport walau groupId-nya io.github.gvjavx.
HTTP 401 a valid token is neededServer dijalankan dengan DDREPORT_TOKEN: kirim Authorization: Bearer <token>.
HTTP 403 requests from other sites are refusedRender dipanggil langsung dari JavaScript browser. Panggil dari backend Anda; token tidak boleh sampai ke browser.
HTTP 403 set DDREPORT_TOKEN to use this server by a name other than localhostServer tanpa token hanya melayani localhost dan alamat IP. Untuk nama domain, isi DDREPORT_TOKEN.
HTTP 503 dengan Retry-AfterAntrean render penuh (empat kali jumlah prosesor). Ulangi sebentar lagi, atau tambah server.
Font di kanvas berbeda dari PDFFont tambahan harus terdaftar di dua tempat: folder DDREPORT_FONTS di server, dan fonts + fontBase di designer. Lihat Font.
Serverless (Vercel, Netlify, Lambda)Menjalankan binary sering dibatasi atau lambat saat mulai. Jalankan server ddreport (Docker) di tempat lain dan panggil lewat HTTP, atau pakai Go langsung.
Laporan besar memakan memoriPDF 100 000 baris butuh ±500 MB. Batasi dengan DDREPORT_MAX_ROWS, bagi per periode, atau pakai XLSX/CSV untuk data mentah.

Butuh dibantu memasangnya? Lihat jasa.