RESTForge

CRUD Composite

Operasi master-detail untuk create, read, dan update data berhierarki

CRUD Composite menyediakan endpoint untuk mengelola data master-detail (header dan detail item) dalam satu request. Seluruh operasi composite dijalankan dalam satu database transaction sehingga jika salah satu bagian gagal, seluruh perubahan di-rollback. Fitur ini cocok untuk transaksi yang memiliki struktur induk-anak, seperti Stock Inbound + Item, Purchase Order + Line Item, atau Invoice + Detail.

Kapan Menggunakan Composite (When to Use Composite)

SkenarioEndpointAlasan
Membuat data header + detail sekaligus/create-compositeSatu transaksi atomik, foreign key detail otomatis terisi
Membaca header beserta seluruh detail item/read-compositeMengembalikan data gabungan header + detail dalam satu response
Memperbarui header + insert/update/delete detail/update-compositeSeluruh perubahan dalam satu transaksi, header totals di-recalculate
Mengubah satu field di tabel tunggal (tanpa detail)/updateGunakan CRUD Dasar untuk entitas tunggal

Konfigurasi Payload (Payload Configuration)

Fitur composite diaktifkan melalui action createComposite, updateComposite, dan readComposite di file payload, disertai konfigurasi masterDetail untuk mendefinisikan relasi header-detail:

payload/stock-inbound.json
{
    "tableName": "stock_inbound",
    "primaryKey": "stock_inbound_id",
    "fieldName": [
        "stock_inbound_id", "inbound_number", "inbound_date",
        "warehouse_id", "supplier_id", "reference_number",
        "notes", "total_items", "total_qty", "total_amount", "status"
    ],
    "action": {
        "datatables": true,
        "create": true,
        "update": true,
        "delete": true,
        "first": true,
        "lookup": true,
        "read": true,
        "createComposite": true,
        "updateComposite": true,
        "readComposite": true
    },
    "masterDetail": {
        "enabled": true,
        "detailTable": "stock_inbound_item",
        "foreignKey": "stock_inbound_id",
        "detailConfig": {
            "tableName": "stock_inbound_item",
            "primaryKey": "stock_inbound_item_id",
            "fieldName": [
                "stock_inbound_item_id", "stock_inbound_id", "line_number",
                "item_product_id", "qty_received", "uom",
                "unit_price", "total_amount", "notes"
            ],
            "requiredFields": [
                "line_number", "item_product_id", "qty_received", "uom", "unit_price"
            ],
            "autoCalculateFields": {
                "total_amount": {
                    "type": "generated",
                    "formula": "qty_received * unit_price"
                }
            }
        },
        "headerCalculations": {
            "total_items": { "type": "count", "source": "items.length" },
            "total_qty": { "type": "sum", "source": "items.qty_received" },
            "total_amount": { "type": "sum", "source": "items.total_amount" }
        },
        "cascadeDelete": true,
        "transactionMode": "required"
    }
}

Penjelasan Konfigurasi (Configuration Reference)

PropertyTipeKeterangan
masterDetail.enabledbooleanMengaktifkan fitur master-detail
masterDetail.detailTablestringNama tabel detail
masterDetail.foreignKeystringKolom foreign key yang menghubungkan detail ke header
detailConfig.primaryKeystringPrimary key tabel detail
detailConfig.fieldNamearrayDaftar kolom yang dikelola di tabel detail
detailConfig.requiredFieldsarrayKolom wajib saat insert detail item
detailConfig.autoCalculateFieldsobjectKolom detail yang dihitung otomatis berdasarkan formula
headerCalculationsobjectKolom header yang dihitung dari data detail (count, sum)
cascadeDeletebooleanJika true, hapus header otomatis menghapus seluruh detail
transactionModestring"required" = seluruh operasi dalam satu transaction

Membuat Data Composite (Creating Composite Data)

Endpoint /create-composite menyisipkan data header beserta seluruh detail item dalam satu transaksi.

Contoh: membuat stock inbound dengan 2 item

POST /api/mini-inventory/stock-inbound/create-composite
{
    "stock_inbound": {
        "inbound_number": "INB/2026/001",
        "inbound_date": "2026-04-16",
        "warehouse_id": "d1000000-0000-0000-0000-000000000000",
        "supplier_id": "b1000000-0000-0000-0000-000000000000",
        "notes": "Pengadaan peralatan kantor",
        "status": "draft",
        "stock_inbound_item": [
            {
                "line_number": 1,
                "item_product_id": "04d71c62-0000-0000-0000-000000000000",
                "qty_received": 25,
                "uom": "pcs",
                "unit_price": 500000
            },
            {
                "line_number": 2,
                "item_product_id": "15e82d73-0000-0000-0000-000000000000",
                "qty_received": 10,
                "uom": "pcs",
                "unit_price": 750000
            }
        ]
    }
}

Request body menggunakan nama tabel header (stock_inbound) sebagai root key, dengan detail item berupa array menggunakan nama tabel detail (stock_inbound_item) sebagai key di dalamnya.

Response (201 Created):

{
    "success": true,
    "message": "stock_inbound data successfully added",
    "data": {
        "stock_inbound_id": "a1b2c3d4-0000-0000-0000-000000000000",
        "inbound_number": "INB/2026/001",
        "inbound_date": "2026-04-16T00:00:00.000Z",
        "warehouse_id": "d1000000-0000-0000-0000-000000000000",
        "supplier_id": "b1000000-0000-0000-0000-000000000000",
        "notes": "Pengadaan peralatan kantor",
        "total_items": 2,
        "total_qty": 35,
        "total_amount": 20000000,
        "status": "draft",
        "created_at": "2026-04-16T10:30:00.000Z",
        "created_by": "Input from API",
        "stock_inbound_item": [
            {
                "stock_inbound_item_id": "e5f6a7b8-0000-0000-0000-000000000001",
                "stock_inbound_id": "a1b2c3d4-0000-0000-0000-000000000000",
                "line_number": 1,
                "item_product_id": "04d71c62-0000-0000-0000-000000000000",
                "qty_received": 25,
                "uom": "pcs",
                "unit_price": 500000,
                "total_amount": 12500000
            },
            {
                "stock_inbound_item_id": "e5f6a7b8-0000-0000-0000-000000000002",
                "stock_inbound_id": "a1b2c3d4-0000-0000-0000-000000000000",
                "line_number": 2,
                "item_product_id": "15e82d73-0000-0000-0000-000000000000",
                "qty_received": 10,
                "uom": "pcs",
                "unit_price": 750000,
                "total_amount": 7500000
            }
        ]
    },
    "timestamp": "2026-04-16T10:30:00.000Z"
}

Field Otomatis saat Create (Auto-Populated Fields)

FieldKeterangan
Primary key headerUUID v4 di-generate otomatis
Primary key detailUUID v4 di-generate otomatis untuk setiap item
Foreign key detailDiisi otomatis dari primary key header yang baru dibuat
total_amount (detail)Dihitung otomatis dari formula qty_received * unit_price berdasarkan autoCalculateFields
total_items (header)COUNT dari detail item berdasarkan headerCalculations
total_qty (header)SUM field qty_received dari seluruh detail item
total_amount (header)SUM field total_amount dari seluruh detail item
created_atTimestamp saat ini, di-set pada header dan setiap detail
created_byDi-set ke "Input from API" (dapat di-override)

Detail item wajib minimal 1 item dalam array. Request dengan array stock_inbound_item kosong atau tanpa array detail akan ditolak dengan error 400.

Membaca Data Composite (Reading Composite Data)

Endpoint /read-composite mengambil data header beserta seluruh detail item dalam satu response. Data detail diambil menggunakan detailQuery dari payload yang dapat menyertakan JOIN ke tabel referensi.

Contoh: mengambil data berdasarkan primary key

POST /api/mini-inventory/stock-inbound/read-composite
{
    "where": [
        { "key": "stock_inbound_id", "value": "a1b2c3d4-0000-0000-0000-000000000000" }
    ]
}

Response (200 OK):

{
    "success": true,
    "data": {
        "stock_inbound_id": "a1b2c3d4-0000-0000-0000-000000000000",
        "inbound_number": "INB/2026/001",
        "inbound_date": "2026-04-16T00:00:00.000Z",
        "warehouse_id": "d1000000-0000-0000-0000-000000000000",
        "supplier_id": "b1000000-0000-0000-0000-000000000000",
        "notes": "Pengadaan peralatan kantor",
        "total_items": 2,
        "total_qty": 35,
        "total_amount": 20000000,
        "status": "draft",
        "stock_inbound_item": [
            {
                "stock_inbound_item_id": "e5f6a7b8-0000-0000-0000-000000000001",
                "stock_inbound_id": "a1b2c3d4-0000-0000-0000-000000000000",
                "line_number": 1,
                "item_product_id": "04d71c62-0000-0000-0000-000000000000",
                "product_code": "PRD-001",
                "product_name": "Keyboard Wireless",
                "qty_received": 25,
                "uom": "pcs",
                "unit_price": 500000,
                "total_amount": 12500000
            },
            {
                "stock_inbound_item_id": "e5f6a7b8-0000-0000-0000-000000000002",
                "stock_inbound_id": "a1b2c3d4-0000-0000-0000-000000000000",
                "line_number": 2,
                "item_product_id": "15e82d73-0000-0000-0000-000000000000",
                "product_code": "PRD-002",
                "product_name": "Mouse Optical",
                "qty_received": 10,
                "uom": "pcs",
                "unit_price": 750000,
                "total_amount": 7500000
            }
        ]
    },
    "timestamp": "2026-04-16T10:30:00.000Z"
}

Detail item pada response /read-composite menyertakan kolom tambahan dari tabel referensi (seperti product_code dan product_name dari tabel item_product) karena detailQuery di payload menggunakan JOIN. Kolom tambahan ini tidak tersedia di endpoint /first yang hanya mengembalikan data header.

Endpoint /read-composite tidak menggunakan cache. Setiap request selalu dijalankan langsung ke database untuk memastikan data yang dikembalikan merupakan versi terbaru.

Memperbarui Data Composite (Updating Composite Data)

Endpoint /update-composite memperbarui data header dan mengelola detail item (insert, update, delete) dalam satu transaksi. Operasi header bersifat partial update, artinya hanya field yang dikirim yang berubah.

Format detail item pada /update-composite berbeda dengan /create-composite. Pada create, detail berupa array. Pada update, detail berupa object dengan tiga properti: insert, update, dan delete.

Update Header Saja (Header Only)

Jika hanya perlu memperbarui field header tanpa menyentuh detail, cukup kirim data header saja:

POST /api/mini-inventory/stock-inbound/update-composite
{
    "stock_inbound": {
        "stock_inbound_id": "a1b2c3d4-0000-0000-0000-000000000000",
        "status": "confirmed",
        "notes": "Sudah diterima dan dicek"
    }
}

Update dengan Operasi Detail (With Detail Operations)

Contoh berikut menunjukkan kombinasi lengkap: menghapus satu item, memperbarui satu item, dan menambahkan satu item baru sekaligus:

POST /api/mini-inventory/stock-inbound/update-composite
{
    "stock_inbound": {
        "stock_inbound_id": "a1b2c3d4-0000-0000-0000-000000000000",
        "notes": "Revisi lengkap detail item",
        "stock_inbound_item": {
            "delete": [
                {
                    "stock_inbound_item_id": "e5f6a7b8-0000-0000-0000-000000000002"
                }
            ],
            "update": [
                {
                    "stock_inbound_item_id": "e5f6a7b8-0000-0000-0000-000000000001",
                    "qty_received": 30,
                    "unit_price": 480000
                }
            ],
            "insert": [
                {
                    "line_number": 3,
                    "item_product_id": "26f93e84-0000-0000-0000-000000000000",
                    "qty_received": 15,
                    "uom": "pcs",
                    "unit_price": 600000
                }
            ]
        }
    }
}

Response (200 OK):

{
    "success": true,
    "message": "stock_inbound data successfully updated",
    "data": {
        "stock_inbound_id": "a1b2c3d4-0000-0000-0000-000000000000",
        "inbound_number": "INB/2026/001",
        "inbound_date": "2026-04-16T00:00:00.000Z",
        "notes": "Revisi lengkap detail item",
        "total_items": 2,
        "total_qty": 45,
        "total_amount": 23400000,
        "status": "draft",
        "updated_at": "2026-04-16T10:30:00.000Z",
        "updated_by": "Update from API",
        "_operations": {
            "deleted": 1,
            "updated": 1,
            "inserted": 1
        }
    },
    "timestamp": "2026-04-16T10:30:00.000Z"
}

Urutan Eksekusi Detail (Detail Execution Order)

Operasi detail dijalankan dalam urutan tetap berikut, terlepas dari urutan properti dalam request body:

UrutanOperasiField WajibKeterangan
1deletePrimary key detailMenghapus item berdasarkan primary key
2updatePrimary key detail + field yang diubahPartial update pada item yang sudah ada
3insertSesuai requiredFields di payloadMenambah item baru, PK dan FK di-generate otomatis

Urutan DELETE → UPDATE → INSERT dipilih untuk menghindari konflik constraint. Menghapus item terlebih dahulu membebaskan unique constraint yang mungkin diperlukan oleh item baru yang akan di-insert.

Kalkulasi Ulang Otomatis (Auto Recalculation)

Setelah seluruh operasi detail selesai, field header yang dikonfigurasi di headerCalculations dihitung ulang secara otomatis berdasarkan state akhir detail item. Properti _operations pada response merangkum jumlah operasi yang berhasil dijalankan.

Perbandingan Format Request (Request Format Comparison)

Aspek/create-composite/update-composite
Primary key headerTidak dikirim (auto UUID)Wajib dikirim
Format detail itemArray [ {...}, {...} ]Object { insert: [], update: [], delete: [] }
Detail item wajibYa (minimal 1 item)Tidak (bisa update header saja)
Foreign key detailOtomatisOtomatis (untuk insert baru)
Header totalsAuto-calculatedAuto-recalculated setelah operasi

Error Umum (Common Errors)

SituasiHTTP CodePenyebab
Root key tidak sesuai nama tabel400Root key harus sesuai dengan tableName di payload
Array detail kosong saat create400/create-composite mewajibkan minimal 1 detail item
Format detail salah saat update400/update-composite memerlukan object {insert, update, delete}, bukan array
PK header tidak dikirim saat update400Primary key header wajib disertakan untuk identifikasi record
PK detail tidak ada pada update/delete400Setiap item di array update dan delete wajib menyertakan primary key detail
Nilai duplicate409Unique constraint violation (misalnya inbound_number sudah ada)
Foreign key tidak ditemukan400Nilai FK merujuk ke record yang tidak ada di tabel referensi
Record header tidak ditemukan404Primary key header yang dikirim tidak ada di database

Langkah Selanjutnya (Next Steps)

On this page