22 KiB
PRD: IBL Sales Dashboard for Marketing Manager and Owner
Date: 2026-06-22
Scope: Design only. No code changes yet.
Project: one-api-lab
Primary source database: one_lab
Primary ETL target database: one_lab_etl
Problem
Saat ini data order, pembayaran, company, MOU, omzet type, dan grouping test masih tersebar di banyak tabel operasional. Ini menyulitkan dua audiens utama:
- Manager marketing yang butuh melihat pertumbuhan omzet, kontribusi company, komposisi layanan, dan peluang follow-up.
- Pemilik perusahaan yang butuh ringkasan revenue, cash-in, piutang, kolektibilitas, dan tren bisnis secara cepat.
Query langsung ke tabel transaksi operasional juga berisiko:
- definisi angka bisa beda-beda antar report,
- agregasi menjadi berat untuk dashboard,
- filter lintas
company,company type group,omzet type,lunas/tagihan, dannat_subgroupsulit distandarkan, - level order dan level item test tercampur.
Goals
- Menyediakan satu sumber data dashboard yang konsisten untuk owner dan manager marketing.
- Mendukung filter:
- tanggal,
- company,
- company type group,
- omzet type,
- status lunas,
- status tagihan/outstanding,
- nat subgroup.
- Memisahkan metrik level order dan level test detail agar angka tidak double count.
- Menyediakan API PHP yang ringan untuk summary, chart, ranking, dan detail table.
- Menjaga definisi bisnis agar konsisten dengan pola join report existing di repo.
Non Goals
- Belum mencakup prediksi sales atau forecasting.
- Belum mencakup target marketing per staff.
- Belum mencakup mutasi invoice, retur, atau akuntansi GL penuh.
- Belum mencakup writeback atau input manual dari dashboard.
Target Users
A. Marketing Manager
Butuh membaca:
- omzet per periode,
- top company,
- kontribusi per
omzet type, - kontribusi per
nat_subgroup, - company mana yang turun,
- company mana yang outstanding-nya tinggi.
B. Owner
Butuh membaca:
- gross sales,
- cash-in,
- outstanding,
- rasio collection,
- distribusi revenue,
- aging tagihan,
- konsentrasi revenue per customer.
Source Tables
Tabel utama yang sudah diverifikasi:
t_orderheadert_orderdetailordert_orderdetailt_testnat_subgroupm_companym_companytypegroupm_companytypem_moum_omzettypef_paymentf_payment_orderheader
Relasi Utama
Relasi order dan company
t_orderheader.T_OrderHeaderM_CompanyID -> m_company.M_CompanyIDm_company.M_CompanyM_CompanyGroupTypeID -> m_companytypegroup.M_CompanyTypeGroupIDm_company.M_CompanyM_CompanyTypeID -> m_companytype.M_CompanyTypeID
Relasi order dan MOU / omzet
t_orderheader.T_OrderHeaderM_MouID -> m_mou.M_MouIDm_mou.M_MouM_OmzetTypeID -> m_omzettype.M_OmzetTypeID
Relasi order dan payment
f_payment.F_PaymentT_OrderHeaderID -> t_orderheader.T_OrderHeaderIDf_payment_orderheader.F_Payment_OrderHeaderID -> t_orderheader.T_OrderHeaderID
Relasi detail dan subgroup
t_orderdetail.T_OrderDetailT_TestID -> t_test.T_TestIDt_test.T_TestNat_SubgroupID -> nat_subgroup.Nat_SubGroupID
Key Business Rules
sales order amountmemakai basist_orderheader.T_OrderHeaderTotal.cash-in amountmemakai basis akumulasif_payment.F_PaymentTotalaktif.lunasmemakai flag utamaf_payment_orderheader.F_Payment_OrderHeaderIsLunas.outstanding amountmemakai prioritas:f_payment_orderheader.F_Payment_OrderHeaderChargeAmountbila tersedia,- fallback
order_total - payment_total.
nat_subgroupadalah atribut level test detail, bukan level order header.- Satu order bisa punya banyak
nat_subgroup, sehingga filter subgroup harus memakai mart detail atau bridge. - Hanya data aktif yang dipakai:
T_OrderHeaderIsActive = 'Y'T_OrderDetailIsActive = 'Y'M_CompanyIsActive = 'Y'M_MouIsActive = 'Y'M_OmzetTypeIsActive = 'Y'Nat_SubGroupIsActive = 'Y'F_PaymentIsActive = 'Y'
- Semua tabel ETL baru ditempatkan di database
one_lab_etl, bukan dione_lab, agar query dashboard tidak membebani database transaksional. - Struktur tabel ETL baru tidak memakai foreign key constraint. Relasi dijaga di level query dan index saja.
- Naming tabel dan kolom baru mengikuti gaya schema inti
one_lab:
- nama tabel: lowercase dengan underscore,
- nama kolom: prefix nama entitas tabel sendiri,
- hindari gaya generik seperti
created_at,updated_at, atau nama kolom tanpa prefix domain.
Product Scope
Dashboard dibagi menjadi 4 blok:
- Executive summary
- Sales composition
- Collection and receivable health
- Customer and subgroup drilldown
KPI Definitions
Core KPI
total_sales: total nilai ordertotal_paid: total pembayaran masuktotal_outstanding: sisa tagihancollection_rate:total_paid / total_saleslunas_order_count: jumlah order lunasopen_order_count: jumlah order belum lunasactive_company_count: jumlah company yang punya transaksi
Breakdown KPI
sales_by_companysales_by_company_type_groupsales_by_omzet_typesales_by_nat_subgroupoutstanding_by_companyoutstanding_by_aging_bucket
Trend KPI
daily_sales_trendmonthly_sales_trenddaily_cashin_trendmonthly_cashin_trend
Dashboard Views
A. Executive Summary
Cards:
- total sales
- total paid
- total outstanding
- collection rate
- lunas order
- active company
Charts:
- line chart
sales vs cash-in - donut
lunas vs belum lunas - bar chart
outstanding aging bucket
B. Sales Composition
Charts:
- bar
sales by omzet type - bar
sales by company type group - horizontal bar
top 10 company by sales - pareto
sales by nat_subgroup
C. Collection Health
Charts:
- bar
top outstanding company - stacked bar
lunas vs belum lunas by omzet type - aging chart
0-30 / 31-60 / 61-90 / 90+ days
D. Drilldown Tables
Tables:
- order summary table
- company ranking table
- subgroup contribution table
- outstanding exception table
Filter Requirements
Global filters:
- date range
- company
- company type group
- omzet type
- payment status:
- all
- lunas
- belum lunas
- tagihan status:
- all
- ada outstanding
- outstanding nol
- nat subgroup
Filter Semantics
Jika filter nat_subgroup dipakai
- KPI summary tetap harus akurat di level order.
- Implementasi awal yang aman:
- summary berbasis mart detail dengan
COUNT(DISTINCT order_id)untuk order count, - nominal sales untuk subgroup memakai
detail_total, - summary order total saat filter subgroup aktif harus diberi label jelas:
sales contribution by subgroup, bukanfull order revenue, agar tidak misleading.
- summary berbasis mart detail dengan
Rekomendasi produk
Pisahkan dua mode saat filter subgroup aktif:
Order view- subgroup dipakai sebagai
has subgroup in order - nilai sales tetap full order total
- subgroup dipakai sebagai
Contribution view- subgroup dipakai pada level detail
- nilai sales memakai
detail_total
Untuk phase 1, lebih aman default ke Contribution view.
Data Model Design
Naming Convention for New ETL Tables
Prinsip penamaan:
- database target:
one_lab_etl - nama tabel mengikuti pola lowercase underscore
- nama kolom mengikuti pola prefix entitas tabel seperti
T_OrderHeader...,M_Company...,F_Payment... - tidak memakai foreign key
- relasi dioptimalkan dengan
PRIMARY KEY,UNIQUE KEY, danINDEX
1. sales_dashboard_order
Grain: 1 row = 1 orderheader
Tujuan:
- dashboard utama owner,
- KPI order-level,
- summary payment / receivable.
Proposed Columns
SalesDashboardOrderID BIGINT PK AUTO_INCREMENT
SalesDashboardOrderT_OrderHeaderID BIGINT NOT NULL
SalesDashboardOrderDate DATETIME NOT NULL
SalesDashboardOrderDateOnly DATE NOT NULL
SalesDashboardOrderT_OrderHeaderLabNumber VARCHAR(25) NULL
SalesDashboardOrderT_OrderHeaderStatus VARCHAR(150) NULL
SalesDashboardOrderM_CompanyID INT NULL
SalesDashboardOrderM_CompanyName VARCHAR(100) NULL
SalesDashboardOrderM_CompanyTypeID INT NULL
SalesDashboardOrderM_CompanyTypeName VARCHAR(50) NULL
SalesDashboardOrderM_CompanyTypeGroupID INT NULL
SalesDashboardOrderM_CompanyTypeGroupName VARCHAR(100) NULL
SalesDashboardOrderM_MouID INT NULL
SalesDashboardOrderM_MouName VARCHAR(100) NULL
SalesDashboardOrderM_OmzetTypeID INT NULL
SalesDashboardOrderM_OmzetTypeName VARCHAR(25) NULL
SalesDashboardOrderTotal DECIMAL(18,2) NOT NULL DEFAULT 0.00
SalesDashboardOrderPaymentTotal DECIMAL(18,2) NOT NULL DEFAULT 0.00
SalesDashboardOrderOutstandingTotal DECIMAL(18,2) NOT NULL DEFAULT 0.00
SalesDashboardOrderIsLunas CHAR(1) NOT NULL DEFAULT 'N'
SalesDashboardOrderHasOutstanding CHAR(1) NOT NULL DEFAULT 'N'
SalesDashboardOrderLastPaymentDate DATE NULL
SalesDashboardOrderAgingDays INT NOT NULL DEFAULT 0
SalesDashboardOrderAgingBucket VARCHAR(20) NOT NULL DEFAULT '0-30'
SalesDashboardOrderDetailCount INT NOT NULL DEFAULT 0
SalesDashboardOrderSourceLastUpdated DATETIME NOT NULL
SalesDashboardOrderCreated DATETIME NOT NULL
SalesDashboardOrderLastUpdated DATETIME NOT NULL
PRIMARY KEY (SalesDashboardOrderID)
UNIQUE KEY SalesDashboardOrderT_OrderHeaderID (SalesDashboardOrderT_OrderHeaderID)
KEY SalesDashboardOrderDateOnly (SalesDashboardOrderDateOnly)
KEY SalesDashboardOrderM_CompanyID (SalesDashboardOrderM_CompanyID)
KEY SalesDashboardOrderM_CompanyTypeGroupID (SalesDashboardOrderM_CompanyTypeGroupID)
KEY SalesDashboardOrderM_OmzetTypeID (SalesDashboardOrderM_OmzetTypeID)
KEY SalesDashboardOrderIsLunas (SalesDashboardOrderIsLunas)
KEY SalesDashboardOrderHasOutstanding (SalesDashboardOrderHasOutstanding)
KEY SalesDashboardOrderAgingBucket (SalesDashboardOrderAgingBucket)
2. sales_dashboard_order_subgroup
Grain: 1 row = 1 orderdetail
Tujuan:
- breakdown
nat_subgroup, - analisis kontribusi layanan,
- drilldown marketing.
Proposed Columns
SalesDashboardOrderSubGroupID BIGINT PK AUTO_INCREMENT
SalesDashboardOrderSubGroupT_OrderDetailID BIGINT NOT NULL
SalesDashboardOrderSubGroupT_OrderHeaderID BIGINT NOT NULL
SalesDashboardOrderSubGroupDate DATETIME NOT NULL
SalesDashboardOrderSubGroupDateOnly DATE NOT NULL
SalesDashboardOrderSubGroupM_CompanyID INT NULL
SalesDashboardOrderSubGroupM_CompanyName VARCHAR(100) NULL
SalesDashboardOrderSubGroupM_CompanyTypeGroupID INT NULL
SalesDashboardOrderSubGroupM_CompanyTypeGroupName VARCHAR(100) NULL
SalesDashboardOrderSubGroupM_MouID INT NULL
SalesDashboardOrderSubGroupM_MouName VARCHAR(100) NULL
SalesDashboardOrderSubGroupM_OmzetTypeID INT NULL
SalesDashboardOrderSubGroupM_OmzetTypeName VARCHAR(25) NULL
SalesDashboardOrderSubGroupT_TestID INT NOT NULL
SalesDashboardOrderSubGroupT_TestCode VARCHAR(25) NULL
SalesDashboardOrderSubGroupT_TestName VARCHAR(100) NOT NULL
SalesDashboardOrderSubGroupNat_SubGroupID INT NULL
SalesDashboardOrderSubGroupNat_SubGroupCode VARCHAR(10) NULL
SalesDashboardOrderSubGroupNat_SubGroupName VARCHAR(50) NULL
SalesDashboardOrderSubGroupPriceTotal DECIMAL(18,2) NOT NULL DEFAULT 0.00
SalesDashboardOrderSubGroupNetTotal DECIMAL(18,2) NOT NULL DEFAULT 0.00
SalesDashboardOrderSubGroupIsLunas CHAR(1) NOT NULL DEFAULT 'N'
SalesDashboardOrderSubGroupSourceLastUpdated DATETIME NOT NULL
SalesDashboardOrderSubGroupCreated DATETIME NOT NULL
SalesDashboardOrderSubGroupLastUpdated DATETIME NOT NULL
PRIMARY KEY (SalesDashboardOrderSubGroupID)
UNIQUE KEY SalesDashboardOrderSubGroupT_OrderDetailID (SalesDashboardOrderSubGroupT_OrderDetailID)
KEY SalesDashboardOrderSubGroupT_OrderHeaderID (SalesDashboardOrderSubGroupT_OrderHeaderID)
KEY SalesDashboardOrderSubGroupDateOnly (SalesDashboardOrderSubGroupDateOnly)
KEY SalesDashboardOrderSubGroupM_CompanyID (SalesDashboardOrderSubGroupM_CompanyID)
KEY SalesDashboardOrderSubGroupM_CompanyTypeGroupID (SalesDashboardOrderSubGroupM_CompanyTypeGroupID)
KEY SalesDashboardOrderSubGroupM_OmzetTypeID (SalesDashboardOrderSubGroupM_OmzetTypeID)
KEY SalesDashboardOrderSubGroupNat_SubGroupID (SalesDashboardOrderSubGroupNat_SubGroupID)
KEY SalesDashboardOrderSubGroupIsLunas (SalesDashboardOrderSubGroupIsLunas)
3. sales_dashboard_etl_log
Tujuan:
- audit ETL,
- troubleshooting refresh,
- monitoring last successful sync.
Proposed Columns
SalesDashboardEtlLogID BIGINT PK AUTO_INCREMENT
SalesDashboardEtlLogJobName VARCHAR(100) NOT NULL
SalesDashboardEtlLogStarted DATETIME NOT NULL
SalesDashboardEtlLogFinished DATETIME NULL
SalesDashboardEtlLogStatus VARCHAR(20) NOT NULL
SalesDashboardEtlLogLastWatermark DATETIME NULL
SalesDashboardEtlLogRowsInserted INT NOT NULL DEFAULT 0
SalesDashboardEtlLogRowsUpdated INT NOT NULL DEFAULT 0
SalesDashboardEtlLogRowsDeleted INT NOT NULL DEFAULT 0
SalesDashboardEtlLogErrorMessage TEXT NULL
SalesDashboardEtlLogCreated DATETIME NOT NULL
PRIMARY KEY (SalesDashboardEtlLogID)
KEY SalesDashboardEtlLogJobName (SalesDashboardEtlLogJobName)
KEY SalesDashboardEtlLogStatus (SalesDashboardEtlLogStatus)
KEY SalesDashboardEtlLogStarted (SalesDashboardEtlLogStarted)
ETL Strategy
Recommended Approach
Gunakan ETL incremental ke tabel fisik di database one_lab_etl, bukan query live langsung dari tabel operasional one_lab untuk semua chart.
Why
- Query dashboard akan jauh lebih cepat.
- Definisi bisnis menjadi terkunci dan konsisten.
- Filter multi-dimensi lebih gampang.
- Aman untuk kebutuhan chart owner yang sering membuka data agregat.
Load Frequency
Phase 1:
- scheduled refresh tiap
30 menit - plus endpoint manual refresh untuk dev/admin
Phase 2 opsional:
- refresh per
5 menitatau near real-time
Watermark Incremental
Gunakan MAX(last_updated) dari sumber:
t_orderheader.T_OrderHeaderLastUpdatedt_orderdetail.T_OrderDetailLastUpdatedf_payment.F_PaymentLastUpdated
Untuk order-level ETL, satu order harus di-rebuild jika salah satu dari tiga sumber berubah.
ETL Steps
Job 1: Build one_lab_etl.sales_dashboard_order
- Ambil order aktif yang berubah sejak watermark.
- Join:
m_companym_companytypem_companytypegroupm_moum_omzettype
- Aggregate payment aktif per order dari
f_payment. - Ambil status lunas dari
f_payment_orderheader. - Hitung:
payment_amountoutstanding_amounthas_outstandingaging_daysaging_bucket
- Upsert ke
one_lab_etl.sales_dashboard_order.
Job 2: Build one_lab_etl.sales_dashboard_order_subgroup
- Ambil detail aktif yang berubah sejak watermark, atau detail milik order yang berubah.
- Join:
t_testnat_subgroupm_companym_companytypegroupm_moum_omzettype
- Tempel status lunas dari hasil order fact.
- Upsert ke
one_lab_etl.sales_dashboard_order_subgroup.
Job 3: ETL log
- Tulis status awal job.
- Simpan jumlah row insert/update.
- Simpan watermark akhir.
- Simpan error jika gagal.
API Design
Controller Proposal
File baru:
application/controllers/dashboard/Sales.php
Endpoint Proposal
1. Summary KPI
Route: GET /dashboard/sales/summary
Purpose:
- cards executive summary
Query params:
date_startdate_endcompany_idcompany_type_group_idomzet_type_idis_lunashas_outstandingnat_subgroup_idview_mode=order|contribution
Response:
{
"status": "OK",
"data": {
"total_sales": 0,
"total_paid": 0,
"total_outstanding": 0,
"collection_rate": 0,
"lunas_order_count": 0,
"open_order_count": 0,
"active_company_count": 0
}
}
2. Sales Trend
Route: GET /dashboard/sales/trend
Purpose:
- line chart sales vs cash-in
Query params:
- semua filter global
granularity=day|month
Response:
{
"status": "OK",
"data": [
{
"period": "2026-06-01",
"sales_amount": 0,
"paid_amount": 0,
"outstanding_amount": 0
}
]
}
3. Breakdown
Route: GET /dashboard/sales/breakdown
Purpose:
- reusable endpoint untuk donut/bar by dimension
Query params:
- semua filter global
dimension=company|company_type_group|omzet_type|nat_subgroup|aging_bucket|lunas_statusmetric=sales|paid|outstanding|order_countlimit
Response:
{
"status": "OK",
"data": [
{
"dimension_id": "1",
"dimension_name": "Kimia Klinik",
"metric_value": 0,
"order_count": 0
}
]
}
4. Company Ranking
Route: GET /dashboard/sales/company-ranking
Purpose:
- top/bottom company
Query params:
- semua filter global
sort_by=sales|paid|outstanding|collection_ratesort_dir=asc|desclimit
Response:
{
"status": "OK",
"data": [
{
"company_id": 0,
"company_name": "",
"company_type_group_name": "",
"omzet_type_name": "",
"sales_amount": 0,
"paid_amount": 0,
"outstanding_amount": 0,
"collection_rate": 0,
"order_count": 0
}
]
}
5. Order Detail Table
Route: GET /dashboard/sales/orders
Purpose:
- detail table order-level
Query params:
- semua filter global
pagepage_sizesort_bysort_dir
Response:
{
"status": "OK",
"data": {
"rows": [
{
"order_id": 0,
"order_date": "",
"lab_number": "",
"company_name": "",
"company_type_group_name": "",
"mou_name": "",
"omzet_type_name": "",
"gross_sales_amount": 0,
"payment_amount": 0,
"outstanding_amount": 0,
"is_lunas": "N",
"aging_bucket": "0-30"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total_rows": 0
}
}
}
6. Subgroup Contribution Table
Route: GET /dashboard/sales/subgroup-contribution
Purpose:
- detail analisis subgroup untuk marketing
Query params:
- semua filter global
pagepage_size
Response:
{
"status": "OK",
"data": {
"rows": [
{
"nat_subgroup_id": 0,
"nat_subgroup_name": "",
"sales_amount": 0,
"detail_count": 0,
"company_count": 0
}
]
}
}
7. ETL Refresh
Route: POST /dashboard/sales/refresh
Purpose:
- manual trigger ETL oleh admin/dev
Response:
{
"status": "OK",
"data": {
"job_name": "sales_dashboard_refresh",
"message": "Refresh started"
}
}
PHP Function Proposal
Di controller dashboard/Sales.php, fungsi yang disarankan:
summary()trend()breakdown()company_ranking()orders()subgroup_contribution()refresh()
Di library/service baru, misalnya application/libraries/SalesDashboardService.php:
buildFilter(array $params): arraygetSummary(array $filters): arraygetTrend(array $filters, string $granularity): arraygetBreakdown(array $filters, string $dimension, string $metric): arraygetCompanyRanking(array $filters, array $paging): arraygetOrders(array $filters, array $paging): arraygetSubgroupContribution(array $filters, array $paging): arrayrunRefresh(?string $forcedFrom = null): arrayrefreshOrderFact(?string $watermark = null): arrayrefreshSubgroupFact(?string $watermark = null): array
SQL Semantics by Use Case
Summary without subgroup filter
Source utama:
one_lab_etl.sales_dashboard_order
Summary with subgroup filter
Source utama:
one_lab_etl.sales_dashboard_order_subgroup
Logic:
- bila
view_mode = contribution, agregasi nominal dari detail fact - bila
view_mode = order, filter order yang punya subgroup terkait lalu join ke order fact
Trend sales vs payment
Gunakan:
- sales berdasarkan
order_date_key - payment berdasarkan
payment_date_lastuntuk phase 1 sederhana
Catatan:
Jika owner butuh cash-in by actual payment day yang akurat penuh, phase berikutnya perlu mart payment harian terpisah.
Recommended Future Extension
Optional Table: sales_dashboard_payment
Grain: 1 row = 1 payment
Dipakai bila nanti butuh:
- cash-in trend murni berdasarkan tanggal bayar,
- payment method analysis,
- rekonsiliasi koleksi lebih detail.
Untuk phase 1, tabel ini opsional.
UAT Checklist
- Summary total sales sesuai order aktif pada periode yang sama.
- Total paid sesuai akumulasi
f_paymentaktif. - Lunas count sesuai flag
f_payment_orderheader. - Outstanding count dan nominal sesuai expected manual sample.
- Filter
companybekerja konsisten di summary, chart, dan table. - Filter
company type groupbekerja konsisten. - Filter
omzet typebekerja konsisten. - Filter
nat_subgroupmenampilkan kontribusi subgroup yang benar. - Top company dan subgroup ranking konsisten dengan sample query manual.
- Refresh ETL tidak membuat duplicate row pada mart.
Risks
- Definisi
lunasdanoutstandingbisa berbeda antara report lama dan kebutuhan dashboard baru. - Jika filter subgroup dipaksa memakai full order value tanpa mode khusus, angka bisa misleading.
- Data payment historis bisa perlu mart terpisah bila owner ingin cashflow by actual payment day.
- Jika update transaksi lama sering terjadi, ETL incremental harus cukup hati-hati memilih order yang direbuild.
Open Questions for Approval
- Saat filter
nat_subgroupaktif, apakah default yang diinginkan:contribution viewberbasis detail amount, atauorder viewberbasis full order amount?
- Untuk KPI owner, apakah
salesmau dianggap:gross order total, ataunet detail totaldari item setelah diskon?
- Apakah cash-in trend perlu by
actual payment datedari awal phase 1? - Apakah perlu dimensi tambahan
marketing/staffkarenam_companypunyaM_CompanyM_StaffID?
Recommended Phase Plan
Phase 1
- buat tabel mart:
sales_dashboard_ordersales_dashboard_order_subgroupsales_dashboard_etl_log- buat endpoint:
summarytrendbreakdowncompany_rankingorderssubgroup_contributionrefresh
Phase 2
- tambah
sales_dashboard_payment - tambah chart cash-in by payment date murni
- tambah analisis staff marketing
- tambah alert outstanding company besar
Recommendation
Saya rekomendasikan lanjut implementasi dengan pendekatan:
- mart fisik untuk order-level dan subgroup-level,
- API PHP berbasis mart, bukan query operasional mentah,
- default subgroup mode =
contribution view, - phase 1 fokus owner + marketing summary yang cepat dan stabil,
- payment mart detail dijadikan phase 2 bila dibutuhkan owner untuk cashflow yang lebih presisi.