Học Power BI Qua Challenges

Học Power BI Qua Challenges – Order Fulfillment KPI [181225]

HỌC POWER BI QUA 100+ CHALLENGES – KHÓA HỌC POWER BI MASTERY THỰC CHIẾN

A – VẤN ĐỀ ĐANG GẶP

B – SẾP MUỐN BẠN LÀM GÌ?

C – POWER BI

D – GIẢI THÍCH CHI TIẾT TỪNG YÊU CẦU

D1 – LOGIC XẾP HẠNG TOP N KHÁCH HÀNG URGENT
D2 – LOGIC TÍNH TOÁN GIÁ TRỊ PENDING VALUE
D3 – LOGIC PHÂN LOẠI TRẠNG THÁI ĐƠN HÀNG & HIỂN THỊ KPI CARD
D4 – LOGIC TÍNH TỶ LỆ HOÀN THÀNH

E – STEP BY STEP – VỪA LÀM VỪA HỌC – LEARNING BY DOING

E1 – Total Quantity Ordered
Total Quantity Ordered = 
SUM(Fact_OrderItems[Quantity])
E2 – Total Quantity Shipped
Total Quantity Shipped = 
SUM(Fact_OrderItems[QuantityShipped])
E3 – Fulfillment Rate – Tính Theo Công Thức Ở D4
Fulfillment Rate = 
DIVIDE(
    [Total Quantity Shipped],
    [Total Quantity Ordered],
    0
)
E4 – Phân Loại Trạng Thái – Tính Theo Logic Ở D3
Line Status Icon = -- Tạo ra một nhãn trạng thái có kèm biểu tượng cho từng dòng đơn hàng, dựa trên tỷ lệ đã giao so với đã đặt.
VAR _Qty = SUM(Fact_OrderItems[Quantity]) -- Tổng số lượng đặt
RETURN
-- Nếu _Qty trống hoặc bằng 0 → trả về BLANK() = không hiển thị
    IF(
        ISBLANK(_Qty) || _Qty = 0,
        BLANK(),
        VAR _Shipped = SUM(Fact_OrderItems[QuantityShipped]) -- Tổng số lượng đã giao
        VAR _Rate = DIVIDE(_Shipped, _Qty, 0) -- Tỷ lệ giao = _Shipped / _Qty
        RETURN
            SWITCH( -- Cách đọc như một chuỗi “Nếu… thì…” theo thứ tự từ trên xuống
                TRUE(),
                _Rate >= 1, "✓ Delivered", -- Nếu _Rate >= 1 → ✓ Delivered
                _Rate >= 0.5, "• Partial", -- Nếu không phải trên, nhưng _Rate >= 0.5 (giao được từ 50% trở lên) → • Partial
                _Rate > 0, "⟲ Processing", -- Nếu không phải trên, nhưng _Rate > 0 (đã giao chút ít) → ⟲ Processing
                "...Pending" -- Còn lại (tức _Rate = 0) → ...Pending (chưa giao gì)
            )
    )
E5 – Line Progress Text
Line Progress Text = -- Tạo ra một dòng text kiểu “đã giao / đã đặt” cho từng dòng dữ liệu.
VAR _Qty = SUM(Fact_OrderItems[Quantity]) -- Tổng số lượng đặt
RETURN
    IF(
        ISBLANK(_Qty) || _Qty = 0, -- Không có dữ liệu hoặc có dữ liệu nhưng tổng bằng 0
        BLANK(),  -- Làm cho ô đó không hiển thị gì -> Không thấy những dòng vô nghĩa kiểu 0/0
        VAR _Shipped = SUM(Fact_OrderItems[QuantityShipped]) -- Tổng đã giao
        -- FORMAT(..., "#,0") = định dạng số có dấu phẩy ngăn cách hàng nghìn: 1500 → 1,500
        RETURN FORMAT(_Shipped, "#,0") & "/" & FORMAT(_Qty, "#,0") -- Đã giao / Đã đặt
    )
E6 – Progress Label
Progress Label = -- Tạo ra 1 dòng text gồm 4 mảnh: % hoàn thành + icon + trạng thái + (shipped/ordered)
VAR _Rate = [Fulfillment Rate] -- Tỷ lệ hoàn thành (ví dụ 0.75 nghĩa là 75%)
VAR _Status = [Progress Status] -- Tên trạng thái (ví dụ “Delivered”, “Processing”, …)
VAR _Icon =
    SWITCH( -- Nếu trạng thái là X thì chọn icon Y
        _Status,
        -- UNICHAR(số): lấy ký tự đặc biệt theo mã Unicode.
        "Delivered", UNICHAR(10003),          -- ✓
        "Partially Shipped", UNICHAR(9680),   -- ◐
        "Processing", UNICHAR(10227),         -- ⟳
        "Pending", UNICHAR(8230),             -- …
        UNICHAR(63)                           -- ?
    )
VAR _ShippedQty = [Total Quantity Shipped] -- Tổng đã shipped/giao
VAR _OrderedQty = [Total Quantity Ordered] -- Tổng đã đặt
RETURN
-- FORMAT(_Rate, "0%") → đổi tỷ lệ thành phần trăm (0.753 → “75%”)
-- & nghĩa là nối chuỗi (ghép chữ lại)
    FORMAT(_Rate, "0%") & "  " & _Icon & " " & _Status &
    " (" & _ShippedQty & "/" & _OrderedQty & ")"
E7 – Order Progress SVG
Order Progress SVG = -- Tự vẽ một thanh tiến độ: nền xám + phần tô màu theo % + dòng chữ mô tả, rồi trả về dưới dạng ảnh SVG để Power BI hiển thị.
VAR _Rate = [Fulfillment Rate] -- Lấy % hoàn thành (tỷ lệ giao hàng). Là số từ 0 đến 1: 0.75 nghĩa là 75%
VAR _BarHeight = 8 -- Thanh cao 8
VAR _TotalWidth = 100 -- Tổng chiều ngang thanh nền là 100
VAR _ProgressWidth = _Rate * _TotalWidth -- Chiều ngang phần “đã hoàn thành” = _Rate * 100
VAR _BarColor = 
    SWITCH(
        TRUE(),
        _Rate >= 1, "#4CAF50",
        _Rate >= 0.75, "#8BC34A",
        _Rate >= 0.5, "#2196F3",
        _Rate >= 0.25, "#FF9800",
        _Rate > 0, "#F44336",
        "#9E9E9E"
    )
VAR _Label = [Progress Label] -- Lấy dòng chữ để hiện cạnh thanh. VD: 75% ◐ Partially Shipped (150/200)
RETURN
"data:image/svg+xml;utf8," & -- Trả về một ảnh dạng data URL
"<svg xmlns='http://www.w3.org/2000/svg' width='250' height='30'>" & -- Kích thước cả tấm ảnh là 250 x 30. Trong đó thanh progress chỉ dài 100 (để chừa chỗ bên phải cho chữ)
-- Vẽ thanh nền. Bắt đầu từ (0,11), dài 100, cao 8
  "<rect x='0' y='11' width='" & _TotalWidth & "' height='" & _BarHeight & "' fill='#EFEBE9'/>" &
-- Vẽ phần tiến độ. Cũng là hình chữ nhật, nằm đè lên nền
-- Dài bao nhiêu phụ thuộc _ProgressWidth (tức % hoàn thành). Màu phụ thuộc _BarColor
  "<rect x='0' y='11' width='" & _ProgressWidth & "' height='" & _BarHeight & "' fill='" & _BarColor & "'/>" &
-- Vẽ chữ nhãn bên phải
-- x = _TotalWidth + 8 nghĩa là đặt chữ bên phải thanh, cách thanh 8 “đơn vị”
-- y='16' để chữ nằm giữa chiều cao ảnh
  "<text x='" & (_TotalWidth + 8) & "' y='16' font-family='Arial Unicode MS' font-size='8' font-weight='600' fill='#0d5620' dominant-baseline='middle'>" &
    _Label &
  "</text>" &
"</svg>"
E8 – Pending Value
Pending Value = -- Tính ra giá trị tiền còn đang chờ giao (Pending Value) của các đơn hàng.
SUMX(
    -- SUMX nghĩa là: đi từng dòng trong bảng Fact_OrderItems,
    -- tính một con số cho dòng đó,
    -- rồi cộng tất cả lại.
    Fact_OrderItems,
    (Fact_OrderItems[Quantity] - Fact_OrderItems[QuantityShipped]) -- Phần tính cho từng dòng → Lấy số còn thiếu = đặt − đã giao
    * Fact_OrderItems[UnitPrice] -- Nhân với đơn giá → ra tiền còn chưa giao của dòng đó)
)
E9 – Min Days Until Due
Min Days Until Due = 
-- Trong các dòng hàng còn chưa giao đủ, dòng nào có hạn giao (DueDate) gần nhất so với hôm nay? Và còn bao nhiêu ngày nữa (hoặc đã trễ bao nhiêu ngày)?
-- Nó lấy số ngày nhỏ nhất trong nhóm đó → tức là deadline gấp nhất.
VAR _RefDate = TODAY() -- 1/ Lấy hôm nay làm mốc
RETURN
CALCULATE( -- Bọc lại bằng CALCULATE để áp điều kiện lọc chưa xong
-- CALCULATE: hãy tính MINX nhưng chỉ trong nhóm dòng hàng chưa xong.
    MINX( -- 4/ Lấy giá trị nhỏ nhất (gấp nhất / trễ nhất)
    -- MINX nghĩa là: tính cho từng dòng, rồi lấy giá trị nhỏ nhất.
    -- Nhỏ nhất ở đây nghĩa là:
    --Nếu có dòng bị trễ (số âm) thì âm nhỏ nhất là trễ nhiều nhất (nguy cấp nhất)
    -- Nếu chưa trễ dòng nào, thì số dương nhỏ nhất là còn ít ngày nhất (gấp nhất)
        Fact_OrderItems,
        DATEDIFF(_RefDate, Fact_OrderItems[DueDate], DAY) -- 3/ Tính còn bao nhiêu ngày tới hạn cho từng dòng
    ),
    Fact_OrderItems[QuantityShipped] < Fact_OrderItems[Quantity] -- 2/ Chỉ xét các dòng hàng chưa xong → đã giao < đã đặt → còn thiếu → chưa hoàn thành.
)
E10 – Tạo Parameter Top N

Modeling → New Parameter → Numeric range

  • Name: Top N Urgent
  • Type: Whole number
  • Min: 1
  • Max: 50
  • Default: 5

Power BI tự động tạo measure [Top N Urgent Value]

E11 – Customer Urgency Rank
Customer Urgency Rank = 
-- Khách nào Due sớm hơn / trễ hơn (Days nhỏ hơn) → ưu tiên trước
-- Nếu Days bằng nhau → khách nào Pending Value lớn hơn → ưu tiên trước
-- Khách không còn đơn chưa xong (Days BLANK) → không xếp hạn

-- Measure luôn phụ thuộc context
-- Khi visual đang ở dòng Customer A, thì:
    -- [Min Days Until Due] là của Customer A - [Pending Value] là của Customer A
-- Nhưng để xếp hạng, bạn không thể chỉ nhìn mỗi A.
-- Bạn cần nhìn tất cả khách trong phạm vi đang chọn để biết A đứng thứ mấy.
-- Vì vậy DAX phải tạo một bảng ảo danh sách khách hàng để đi so sánh.

VAR _ThisDays = [Min Days Until Due] -- số ngày gấp nhất của customer đang xét (dòng hiện tại).
VAR _ThisPending = COALESCE([Pending Value], 0) -- tiền pending của customer hiện tại.
-- COALESCE(x,0) nghĩa là: nếu x trống thì coi như 0
-- -> Tôi đang xếp hạng cho ai? Tôi phải giữ lại giá trị của người đó trước

-- Tạo bảng ảo gồm danh sách khách + dán thêm cột Days và Pending
VAR _Customers =
    -- Nó tạo bảng ảo có thêm cột __Days và __Pending
    -- Mỗi dòng (mỗi customer) trong bảng ảo sẽ được tính [Min Days Until Due] và [Pending Value] trong ngữ cảnh của customer đó.
    -- -> Tôi lập một bảng danh sách khách và ghi sẵn Days/Pending của từng khách vào bảng đó.
    ADDCOLUMNS( 
        -- Lấy toàn bộ khách hàng trong phạm vi người dùng đang chọn (filter/slicer).
        -- Không bị giới hạn bởi đang ở một customer nào đó trên dòng hiện tại.
        -- -> Tôi cần danh sách đối thủ để so rank, nhưng vẫn tôn trọng filter người dùng.
        ALLSELECTED(Dim_Customer), 
        "__Days", [Min Days Until Due],
        "__Pending", COALESCE([Pending Value], 0)
    )

-- Lọc chỉ những khách còn đơn chưa xong
VAR _Valid =
    FILTER(
        _Customers,
        NOT ( ISBLANK([__Days]) ) -- Nếu __Days BLANK: nghĩa là khách đó không còn dòng chưa xong → loại ra.
    )

-- Offset = số khách có Days nhỏ hơn (gấp hơn) -> họ luôn đứng trước
VAR _Offset = -- Bao nhiêu người chắc chắn xếp trước.
    COUNTROWS(
        FILTER(
            _Valid, -- 1/ Trong danh sách _Valid,
            [__Days] < _ThisDays -- 2/ đếm xem có bao nhiêu khách có __Days nhỏ hơn _ThisDays.
            -- Vì Days càng nhỏ càng gấp, nên:
                -- Những khách có Days nhỏ hơn chắc chắn đứng trước bạn bất kể tiền.
                -- VD: Nếu có 7 khách có Days < Days của bạn → bạn chắc chắn không thể nằm trong Top 7, mà phải bắt đầu từ hạng 8 trở đi (trước khi xét tie-break).
        )
    )

-- Trong nhóm Days bằng nhau: rank theo Pending (tiền lớn trước)
VAR _RankWithinSameDay =
    RANKX( -- 2/ Xếp hạng trong nhóm đó theo Pending:
        FILTER(_Valid, [__Days] = _ThisDays), -- 1/ Chỉ lấy nhóm khách có Days bằng bạn.
        [__Pending],
        _ThisPending,
        DESC, -- DESC: tiền lớn hơn đứng trước
        Dense -- Nếu bằng điểm thì hạng không nhảy số
    )
RETURN
    IF(
        ISBLANK(_ThisDays),
        BLANK(),
        _Offset + _RankWithinSameDay
    )
E12 – Is Top N Urgent
Is Top N Urgent = 
-- Measure này là một cái công tắc để đánh dấu khách nào nằm trong Top N khách gấp nhất. Nó trả về:
    -- 1 = đúng, khách này thuộc Top N urgent
    -- 0 = không thuộc Top N (hoặc không có rank)
-- Measure này hoạt động theo ngữ cảnh dòng hiện tại

-- Measure này dùng để làm gì trong Power BI?
    -- Làm filter: chỉ show khách Is Top N Urgent = 1
    -- Làm conditional formatting: tô màu/đánh dấu Top N
    -- Làm KPI: đếm số khách đang urgent trong Top N

VAR _TopN = [Top N Urgent Value] -- Người dùng chọn Top N từ slicer
VAR _Rank = [Customer Urgency Rank] -- Measure bạn đã làm trước đó. VD: Với khách A có thể ra 1, khách B ra 7, khách C BLANK (nếu không còn pending).
RETURN
    IF(
        -- Nếu Rank không trống (Có pending/được xếp hạng) và Rank ≤ TopN
        NOT(ISBLANK(_Rank)) && _Rank <= _TopN,
        1, -- → trả 1
        0 -- Ngược lại → trả 0
        -- DAX không lọc trực tiếp ở đây, nó chỉ trả ra 1/0 cho từng dòng theo context,
        -- Cái 1/0 này giúp bạn dễ dàng dùng làm bộ lọc hoặc highlight trong báo cáo.
    )
E13 – Cấu Hình Quan Trọng

Measure [Order Progress SVG] phải được set: Measure toolsData categoryImage URL

Cấu hình Image (thanh SVG)

  • Format (icon cọ sơn) → Image
  • Toggle: On
  • Image type: Image URL
  • Image: Nhấn fx → Chọn [Order Progress SVG]
  • Position: Below Text
  • Size: 900px