13 KiB
Migrasi profit sharing
4 Oktober 2026
Ringkasan
Ada tiga perubahan di backend:
- Parent category bisa ditandai bukan team. Kategori punya field baru
is_team. Parent category denganis_team: falsetidak bisa dipilih sebagai team di purchase order dan cash advance, dan tidak ikut laporan profit sharing. - Endpoint laporan pindah path.
/api/v1/analytics/parent-categoriesmenjadi/api/v1/analytics/profit-sharing. - Pembagian revenue tinggal dua porsi. Porsi purchase (60%) dihapus. Revenue sekarang dibagi ke owner (SDL) dan team, dengan porsi team = 100% − fee owner.
Nomor 2 dan 3 adalah breaking change. Setelah backend baru dirilis, client yang masih memanggil path lama mendapat 404, dan limit_purchase serta percentages.purchase tidak ada lagi di response. Karena itu client harus diupdate lebih dulu, lihat Urutan rilis.
Yang perlu bertindak, di dashboard maupun app mobile, mana pun yang punya layarnya:
- Form kategori: tambah toggle team untuk parent category.
- Laporan profit sharing: ganti path, hapus porsi purchase, tampilkan dua porsi.
- Form purchase order dan cash advance: picker team tidak perlu diubah, tapi form edit perlu menyesuaikan, lihat Purchase order dan cash advance.
Kategori: field is_team
Response
Semua response kategori membawa is_team (boolean, tidak pernah null): POST /api/v1/categories, PUT /api/v1/categories/:id, GET /api/v1/categories, dan GET /api/v1/categories/:id.
{
"id": "<uuid>",
"name": "Merchandise",
"parent_id": null,
"owner_fee_percent": null,
"is_team": false
}
Request
POST /api/v1/categories dan PUT /api/v1/categories/:id menerima is_team.
{
"name": "Merchandise",
"is_team": false
}
| Request | is_team yang dikirim |
Hasil |
|---|---|---|
| Create | tidak dikirim | true |
| Create | false |
false |
| Update | tidak dikirim atau null |
tidak berubah |
| Update | true atau false |
diganti |
Update yang hanya berisi is_team diterima.
Aturan nilainya:
is_teamhanya berpengaruh di parent category, yaitu kategori denganparent_id: null. Di sub-category nilainya disimpan tapi tidak dipakai, jadi tampilkan toggle hanya untuk parent category.- Semua kategori yang sudah ada sebelum rilis bernilai
true. Tidak ada yang berubah sampai admin mematikannya. - Flag dibaca saat request, bukan saat transaksi. Kalau parent category dimatikan, penjualannya di periode lampau juga hilang dari laporan profit sharing. Kalau dinyalakan lagi, semuanya muncul kembali.
Efek is_team: false
| Endpoint | Efek |
|---|---|
GET /api/v1/purchase-orders/teams, GET /api/v1/cash-advances/teams |
Kategori tidak muncul. Pusat tetap ada. |
| Create dan update purchase order dan cash advance | team_scope: "category" dengan team_category_id kategori ini ditolak. |
GET /api/v1/analytics/profit-sharing |
Kategori tidak muncul di data, dan revenue-nya tidak dihitung di budget. |
GET /api/v1/analytics/profit-sharing/:parent_category_id |
Ditolak. |
Data yang sudah ada tidak diubah. Purchase order dan cash advance yang sudah tercatat ke kategori itu tetap menyimpan team-nya. Laporan purchasing (GET /api/v1/analytics/purchasing) masih menampilkannya di team_data, dan filter team=<category_id> tetap bisa dipakai.
Laporan lain yang tidak menyaring is_team, misalnya GET /api/v1/analytics/categories, tetap menampilkan penjualan kategori itu.
Purchase order dan cash advance
Picker team sudah mengambil dari GET /api/v1/purchase-orders/teams dan GET /api/v1/cash-advances/teams, jadi kategori non-team otomatis tidak muncul tanpa perubahan di client.
Yang perlu diubah ada di form edit. Purchase order atau cash advance lama bisa tercatat ke kategori yang sekarang sudah non-team. Kalau form edit mengirim ulang team_scope dan team_category_id yang sama, request ditolak, walaupun user tidak mengubah team-nya.
- Kirim
team_scopedanteam_category_idhanya kalau user mengganti team. Kalauteam_scopetidak dikirim, team yang tersimpan tidak berubah. - Kalau team yang tersimpan tidak ada di daftar
/teams, tampilkan namanya dari fieldteamdi response apa adanya, dan jangan memilihkan team lain secara otomatis.
Contoh error saat team ditolak, dengan HTTP status 500:
{
"success": false,
"data": null,
"errors": [
{
"code": "900",
"entity": "purchase_order_service",
"cause": "category Merchandise is not a team"
}
]
}
Untuk cash advance, entity bernilai cash_advance_service. Cocokkan dengan teks is not a team di cause kalau perlu menampilkan pesan khusus.
Laporan profit sharing
Path baru
| Lama | Baru |
|---|---|
GET /api/v1/analytics/parent-categories |
GET /api/v1/analytics/profit-sharing |
GET /api/v1/analytics/parent-categories/:parent_category_id |
GET /api/v1/analytics/profit-sharing/:parent_category_id |
Query parameter dan role tidak berubah:
date_fromdandate_towajib, dengan formatDD-MM-YYYY, misalnya28-09-2026.outlet_idopsional.- Hanya bisa diakses superadmin, admin, manager, owner, dan purchasing.
Path lama sudah tidak ada dan mengembalikan 404.
Pembagian revenue
Revenue tiap parent category yang team dibagi dua:
- SDL (fee owner) = revenue ×
owner_fee_percent/ 100. Default-nya 20%, dan bisa diganti per parent category lewatowner_fee_percentdi kategori. - Team = revenue − SDL.
Contoh dengan tiga parent category dalam satu minggu:
| Parent category | is_team |
Fee owner | Revenue | SDL | Team |
|---|---|---|---|---|---|
| Food | true |
20% (default) | 1.000.000 | 200.000 | 800.000 |
| Drink | true |
35% | 2.000.000 | 700.000 | 1.300.000 |
| Merchandise | false |
- | 500.000 | tidak dihitung | tidak dihitung |
| Budget minggu ini | 3.000.000 | 900.000 | 2.100.000 |
Perubahan field
| Field | Sebelum | Sesudah |
|---|---|---|
data[] |
semua parent category | hanya parent category team |
budget.percentages.purchase |
60 |
dihapus |
budget.percentages.owner |
20, atau fee parent itu di endpoint detail |
tidak berubah |
budget.percentages.team |
20 |
100 − owner: 80 di list, 100 − fee parent di detail |
limit_purchase di budget.total, budget.weekly[], budget.monthly[] |
60% revenue | dihapus |
limit_team di tempat yang sama |
20% revenue | revenue − sdl |
revenue dan sdl di budget |
semua parent category | hanya parent category team |
Di endpoint list, budget.percentages selalu berisi default 20 dan 80, walaupun ada parent dengan fee berbeda. Angka sdl dan limit_team dihitung per parent dengan fee masing-masing, jadi limit_team / revenue bisa tidak persis 80%. Tampilkan angka rupiah dari response, jangan dihitung ulang dari persentase.
Baris di data[] membawa sdl tapi tidak membawa porsi team. Kalau porsi team per parent perlu ditampilkan, hitung dari total_revenue − sdl.
Contoh response list, dipotong:
{
"success": true,
"data": {
"date_from": "2026-09-28T00:00:00+07:00",
"date_to": "2026-10-04T23:59:59.999999999+07:00",
"data": [
{
"parent_category_id": "<uuid>",
"parent_category_name": "Drink",
"owner_fee_percent": 35,
"sdl": 700000,
"total_revenue": 2000000
},
{
"parent_category_id": "<uuid>",
"parent_category_name": "Food",
"owner_fee_percent": 20,
"sdl": 200000,
"total_revenue": 1000000
}
],
"budget": {
"percentages": { "owner": 20, "team": 80 },
"cut_off_from": "2026-09-28T00:00:00+07:00",
"cut_off_to": "2026-10-04T23:59:59.999999999+07:00",
"total": {
"period_start": "2026-09-28T00:00:00+07:00",
"period_end": "2026-10-04T23:59:59.999999999+07:00",
"revenue": 3000000,
"order_count": 4,
"sdl": 900000,
"limit_team": 2100000
},
"weekly": [
{
"period_start": "2026-09-28T00:00:00+07:00",
"period_end": "2026-10-04T23:59:59.999999999+07:00",
"revenue": 3000000,
"order_count": 4,
"sdl": 900000,
"limit_team": 2100000
}
],
"monthly": [
{
"month": "2026-09",
"week_count": 1,
"period_start": "2026-09-28T00:00:00+07:00",
"period_end": "2026-10-04T23:59:59.999999999+07:00",
"revenue": 3000000,
"order_count": 4,
"sdl": 900000,
"limit_team": 2100000
}
]
}
},
"errors": null
}
Di endpoint detail, budget bentuknya sama, tapi percentages memakai fee parent itu, misalnya { "owner": 35, "team": 65 } untuk Drink.
Detail kategori non-team
Detail untuk parent category non-team ditolak dengan HTTP status 500:
{
"success": false,
"data": null,
"errors": [
{
"code": "internal_error",
"entity": "AnalyticsHandler::GetParentCategoryAnalyticsDetail",
"cause": "failed to get parent category analytics detail: failed to get parent category analytics detail: category Merchandise is not a team"
}
]
}
Ini bisa terjadi kalau user membuka link lama atau bookmark ke parent yang baru dimatikan. Cocokkan dengan teks is not a team di cause, lalu arahkan user kembali ke list.
Migrasi client
Laporan profit sharing
- Ganti path ke
/api/v1/analytics/profit-sharing. Selama backend lama masih jalan, path baru mengembalikan 404. Kalau dapat 404, panggil path lama/api/v1/analytics/parent-categories, supaya client baru bisa dirilis sebelum backend. - Hapus tampilan limit purchase dan persentase purchase. Jangan menganggap
limit_purchaseataupercentages.purchaseselalu ada. - Tampilkan dua porsi dengan label "SDL / Fee owner" dan "Team", ambil angkanya dari
sdldanlimit_team. - Kalau detail ditolak dengan
is not a team, arahkan kembali ke list.
Selama fallback ke backend lama, limit_team masih berisi 20% revenue. Angkanya baru jadi revenue − sdl setelah backend baru dirilis.
Form kategori
- Tambah toggle
is_teamuntuk parent category, misalnya berlabel "Ikut profit sharing (team)". Untuk kategori baru, toggle menyala secara default. - Saat membuka form edit, isi toggle dari
is_team. Selama backend lama masih jalan, field ini tidak ada di response, jadi anggaptrue. - Saat admin mematikan toggle, tampilkan konfirmasi bahwa kategori itu tidak akan muncul di pilihan team dan di laporan profit sharing, termasuk untuk periode lampau.
- Di daftar kategori, beri penanda untuk parent category dengan
is_team: false.
Backend lama mengabaikan is_team di request, jadi toggle bisa dirilis lebih dulu, tapi belum berpengaruh sampai backend baru jalan. Pengecualiannya update yang hanya berisi is_team: backend lama menolaknya dengan error code 303 dan pesan at least one field must be provided for update. Selama masa transisi, kirim is_team bersama field form lainnya.
Database dan urutan rilis
Migration 000101_add_is_team_to_categories menambah kolom categories.is_team (BOOLEAN NOT NULL DEFAULT TRUE). Semua kategori yang ada otomatis bernilai true. Migration ini hanya menambah kolom, jadi backend lama tetap jalan normal setelahnya.
Urutan rilis:
- Rilis client baru: path profit sharing dengan fallback ke path lama, tanpa porsi purchase, dan dengan toggle
is_team. Untuk app mobile, pastikan versi baru sudah dipakai sebagian besar user sebelum langkah 3, karena versi lama yang memanggil/parent-categoriesmendapat 404 setelah itu. - Jalankan migration
000101. - Deploy backend baru.
- Hapus fallback path lama di client.
- Admin mematikan
is_teamdi parent category yang bukan team.
Rollback: deploy backend lama, lalu jalankan down migration yang menghapus kolom is_team. Client dengan fallback tetap jalan di backend lama. Nilai is_team yang sudah diatur admin hilang saat kolom dihapus.
Checklist
- Client baru (fallback path, tanpa porsi purchase, toggle
is_team) dirilis - Migration
000101dan backend baru dirilis di staging - Uji: parent category yang dimatikan hilang dari
/purchase-orders/teamsdan/cash-advances/teams - Uji: purchase order dan cash advance baru dengan kategori itu ditolak, dan edit purchase order lama tanpa mengganti team tetap berhasil
- Uji: kategori itu hilang dari
/analytics/profit-sharing, dan detailnya ditolak - Uji:
limit_team = revenue − sdl, dansdlmengikuti fee masing-masing parent - Migration
000101dan backend baru dirilis di production - Fallback path lama di client dihapus
FAQ
Kenapa porsi team jadi 100% − fee owner, bukan tetap 20%? Porsi purchase sudah tidak ada, jadi seluruh revenue dibagi dua. Owner mengambil fee-nya, dan sisanya untuk team. Kalau fee owner sebuah parent dinaikkan, porsi team parent itu turun sebesar yang sama.
Apakah sub-category bisa dijadikan non-team sendiri? Tidak. Team dan profit sharing dihitung per parent category, jadi semua sub-category ikut status parent-nya.
Bagaimana dengan kategori top-level yang tidak punya sub-category? Kategori itu tetap parent category, jadi is_team berlaku untuknya.