Tồn kho an toàn: đặt hàng theo trung bình là hết hàng gần một nửa số lần
Tính điểm đặt hàng lại, tồn kho an toàn và số máy nhập cho một đợt khuyến mãi bằng xác suất, rồi kiểm từng công thức bằng mô phỏng 1.000 năm bán hàng.
16:40 thứ Bảy 2026-09-26, cửa hàng số 58 của BanHang ở Huế bán chiếc SP-00412 cuối cùng, một mẫu điện thoại giá vốn 5.600.000 đ. ERP đã tạo phiếu nhập 60 máy từ kho tổng Đà Nẵng lúc 12:00 thứ Ba, khi tồn còn 24 máy: đủ bán 4 ngày theo trung bình 6 máy mỗi ngày, bằng thời gian giao trung bình của kho. Khách không mua nhiều hơn thường ngày. Chỉ là lần này xe về trưa Chủ nhật, sau 5 ngày, và trong gần 20 giờ chờ có 5 khách hỏi mua rồi ra về. Đó là lần hết hàng thứ hai của mẫu này trong tháng 9 (giờ giấc, số khách và giá là minh họa). Bài này tính lại điểm đặt hàng bằng xác suất, kiểm công thức bằng mô phỏng 1.000 năm bán hàng, rồi giải bài toán anh em: nhập bao nhiêu máy cho một đợt khuyến mãi chỉ bán một lần.
Đọc nhanh
- Đặt hàng khi tồn bằng nhu cầu trung bình trong lead time là chấp nhận hết hàng ở gần một nửa số chu kỳ: mô phỏng đếm 15,4 lần hết hàng và 128 máy bị mất mỗi năm.
- Tồn kho an toàn SS = z·σ_DL, với σ_DL = √(L̄·σ_d² + d̄²·σ_L²) khi lead time dao động. Bán 6 ± 3,46 máy/ngày, giao sau 4 ± 0,95 ngày: mức 95% cần đặt khi còn 39 máy. Quên độ lệch của lead time thì ra 36 máy và chỉ đạt 90%.
- Phân phối chuẩn có đuôi mỏng hơn nhu cầu thật: mục tiêu 99% chỉ đạt 97,80%, số lần hết hàng gấp 2,3 lần công thức hứa. Từ 95% lên 99,9%, vốn nằm trong kho tăng từ 255,3 lên 327,8 triệu đồng cho một mẫu ở một cửa hàng.
- Hàng chỉ bán một đợt thì nhập tới phân vị Cu/(Cu + Co) của nhu cầu. Thiếu mất lãi 790.000 đ, thừa lỗ 500.000 đ: tỉ số 0,6124, nhập 1.086 máy cho dự báo 1.000 ± 300, đúng chỗ mô phỏng cho lợi nhuận kỳ vọng lớn nhất.
1. Hàng chỉ hết trong lead time
ERP của cửa hàng chạy chính sách điểm đặt hàng lại (reorder point, ROP). Sau mỗi lần bán, nó tính vị thế tồn bằng tồn trên kệ cộng hàng đang về; vị thế tồn xuống tới ROP thì tạo phiếu nhập Q = 60 máy. Số tồn đó đúng nhờ mỗi đơn trừ kho bằng câu UPDATE có điều kiện ở bài transaction.
Lúc phiếu được tạo, trên kệ còn đúng ROP máy và không có lô nào đang về. Hàng chỉ có thể hết trong lead time, từ lúc đặt tới lúc lô về. Gọi D_L là số máy khách muốn mua trong khoảng đó: cửa hàng hết hàng khi và chỉ khi D_L > ROP. Đặt ROP = 24, đúng trung bình của D_L, là hết hàng mỗi khi lead time bán nhiều hơn trung bình, tức khoảng một nửa số chu kỳ.
Nhu cầu mỗi ngày là số đếm, nên ứng viên đầu tiên là phân phối Poisson. Poisson đúng khi khách đến độc lập với tốc độ không đổi, và khi đó phương sai bằng trung bình. Bài giả định lịch sử 180 ngày cho trung bình 6,0 máy và phương sai 12 (minh họa), gấp đôi trung bình vì có ngày lương, ngày mưa, ngày đối thủ giảm giá. Mô hình hợp hơn cho mỗi ngày một tốc độ riêng λ theo Gamma(6, 1) và số bán Poisson(λ). Hỗn hợp gamma–Poisson này là phân phối nhị thức âm, phương sai 6 × (1 + 6/6) = 12, tức σ_d = 3,46 máy. Lead time cũng ngẫu nhiên: kho giao sau 3 ngày ở 35% số lần, 4 ngày 40%, 5 ngày 15%, 6 ngày 10% (minh họa), trung bình L̄ = 4,0 ngày, độ lệch chuẩn σ_L = 0,949 ngày.
2. Tồn kho an toàn theo mức phục vụ chu kỳ
Mức phục vụ chu kỳ (cycle service level, CSL) là xác suất một chu kỳ nhập hàng kết thúc mà không hết hàng lần nào. Muốn CSL cho trước thì ROP phải là phân vị tương ứng của D_L. Công thức quen thuộc xấp xỉ D_L bằng phân phối chuẩn, phần cộng thêm trên trung bình gọi là tồn kho an toàn (safety stock, SS):
ROP = μ_DL + SS, μ_DL = d̄·L̄
SS = z·σ_DL, z = Φ⁻¹(CSL), Φ là hàm phân phối của chuẩn tắc
σ_DL = σ_d·√L khi lead time cố định
σ_DL = √(L̄·σ_d² + d̄²·σ_L²) khi lead time dao động, độc lập với nhu cầu
Cửa hàng 58, mục tiêu 95% (z = 1,645):
μ_DL = 6 × 4 = 24 máy
Lead time cố định: σ_DL = 3,46 × √4 = 6,93 SS = 1,645 × 6,93 = 11,4 ROP = 35,4 → 36
Lead time dao động: σ_DL = √(4 × 12 + 6² × 0,9) = √(48 + 32,4) = √80,4 = 8,97
SS = 1,645 × 8,97 = 14,8 ROP = 24 + 14,8 = 38,8 → 39
Độ lệch 0,95 ngày của lead time góp 32,4 trong tổng phương sai 80,4, tức 40%; bỏ quên nó thì tồn kho an toàn thiếu gần một phần tư. ROP làm tròn lên, vì làm tròn xuống thì hụt mục tiêu.
CSL đếm chu kỳ, không đếm khách. Thước đo thứ hai là fill rate: phần nhu cầu được bán từ hàng có sẵn. Số máy thiếu kỳ vọng mỗi chu kỳ là σ_DL·G(k), với k = SS/σ_DL và hàm mất mát chuẩn đơn vị G(k) = φ(k) − k·(1 − Φ(k)), φ là hàm mật độ của chuẩn tắc. Từ đó fill rate = 1 − σ_DL·G(k)/Q.
Quy tắc cũ ROP = 24 có k = 0, G(0) = 1/√(2π) = 0,3989. Mỗi chu kỳ thiếu kỳ vọng 8,97 × 0,3989 = 3,58 máy trên 60 máy, fill rate = 1 − 3,58/60 = 94,0%. Hết hàng ở một nửa số chu kỳ mà 94% khách vẫn mua được, vì mỗi lần hết hàng chỉ thiếu trung bình 3,58/0,5 = 7,2 máy, so với 60 máy bán trong chu kỳ. Chopra, Reinhardt và Dada (2004) ghi nhận điều tương tự: nhiều công ty nhắm fill rate 97–99%, mà trong ví dụ của họ mức đó ứng với CSL chỉ 50–70%. CSL cho biết bao lâu hết hàng một lần, fill rate cho biết mất bao nhiêu khách, và fill rate còn tăng theo Q dù ROP giữ nguyên.
3. Mô phỏng 1.000 năm bán hàng
Script chạy cửa hàng 58 trong 365.000 ngày cho mỗi ROP, cùng một dòng khách cho mọi ROP (hạt giống 2026). Mỗi khách mua một máy, giờ đến rải đều trong ngày, hết hàng thì khách ra về và máy đó tính là mất. Một chu kỳ là khoảng giữa hai lần lô về. Vốn tồn kho là tồn trên kệ trung bình theo thời gian nhân giá vốn. csl_dung tính đúng P(D_L ≤ ROP): tổng L ngày nhị thức âm (6; 0,5) là nhị thức âm (6L; 0,5), lấy trung bình theo phân phối của L. Hai chế độ cuoi_ngay và theo_tuan dùng cho mục 6.
import math, sys
import numpy as np
import scipy
from scipy.stats import nbinom, norm
D_TB, D_VAR = 6, 12 # máy/ngày: tốc độ mỗi ngày Gamma(6, 1), số bán Poisson
LT, LT_P = [3, 4, 5, 6], [0.35, 0.40, 0.15, 0.10]
Q, GIA_VON, NAM = 60, 5_600_000, 1000
L_TB = sum(l * p for l, p in zip(LT, LT_P))
L_VAR = sum(l * l * p for l, p in zip(LT, LT_P)) - L_TB ** 2
MU = D_TB * L_TB
SIGMA = math.sqrt(L_TB * D_VAR + D_TB ** 2 * L_VAR) # có lead time biến thiên
SIGMA_CO_DINH = math.sqrt(D_VAR * L_TB) # bỏ quên độ lệch lead time
def nhu_cau(che_do, rng): # số máy khách muốn mua mỗi ngày
so_ngay = 365 * NAM
if che_do == "theo_tuan": # tốc độ giữ nguyên cả tuần; phân phối từng ngày không đổi
return rng.poisson(np.repeat(D_TB * rng.gamma(6, 1 / 6, so_ngay // 7 + 1), 7)[:so_ngay])
return rng.poisson(rng.gamma(6, 1, so_ngay))
def mo_phong(rop, che_do="doc_lap", seed=2026): # rop < Q: mỗi lúc tối đa một lô đang về
rng = np.random.default_rng(seed)
n = nhu_cau(che_do, rng)
gio = np.sort(np.repeat(np.arange(len(n)), n) + rng.random(n.sum())).tolist()
lead = iter(rng.choice(LT, p=LT_P, size=len(n)).tolist())
ton = vi_the = rop + Q # tồn trên kệ; vị thế tồn = trên kệ + đang về
ve, chu_ky, het, ky_nay_het, mat, dien_tich, t0, ngay0 = math.inf, 0, 0, 0, 0, 0.0, 0.0, 0
for t in gio: # mỗi phần tử là giờ một khách muốn mua một máy
if che_do == "cuoi_ngay" and int(t) > ngay0: # ERP chỉ xét đặt hàng lúc đóng cửa
if vi_the <= rop:
ve = ngay0 + 1 + next(lead); vi_the += Q
ngay0 = int(t)
if ve <= t: # lô hàng về: hết một chu kỳ
dien_tich += ton * (ve - t0); t0 = ve
ton += Q; ve = math.inf; chu_ky += 1; het += ky_nay_het; ky_nay_het = 0
dien_tich += ton * (t - t0); t0 = t
if ton == 0: # khách ra về tay không: một máy bị mất
mat += 1; ky_nay_het = 1; continue
ton -= 1; vi_the -= 1
if che_do != "cuoi_ngay" and vi_the <= rop: # ERP xét ngay sau mỗi lần bán
ve = t + next(lead); vi_the += Q
return 1 - het / chu_ky, chu_ky / NAM, het / NAM, mat / NAM, 1 - mat / len(gio), dien_tich / t0
G = lambda k: norm.pdf(k) - k * norm.sf(k) # hàm mất mát chuẩn đơn vị
csl_dung = lambda rop: sum(p * nbinom.cdf(rop, 6 * l, 0.5) for l, p in zip(LT, LT_P))
print(f"Python {sys.version.split()[0]}, numpy {np.__version__}, scipy {scipy.__version__}, {NAM} năm")
print(f"mu_DL = {MU:.1f}, sigma_L = {math.sqrt(L_VAR):.3f}, sigma_DL = {SIGMA:.2f}, bỏ quên sigma_L: {SIGMA_CO_DINH:.2f}")
dong = [(m, "chuan", math.ceil(MU + norm.ppf(m) * SIGMA)) for m in [0.50, 0.90, 0.95, 0.99, 0.999]]
dong += [(m, "dung", next(r for r in range(Q) if csl_dung(r) >= m)) for m in [0.95, 0.99, 0.999]]
print("muc cach rop csl_ct csl_dung csl_mp ck/nam het/nam mat/nam fr_ct fr_mp von_trieu")
for muc, cach, rop in dong:
k = (rop - MU) / SIGMA
csl, ck, het, mat, fr, ton_tb = mo_phong(rop)
print(f"{muc:<5} {cach:<5}{rop:4d} {norm.cdf(k):7.2%} {csl_dung(rop):8.2%} {csl:7.2%} {ck:6.1f} {het:7.2f}"
f" {mat:7.1f} {1 - SIGMA * G(k) / Q:7.2%} {fr:7.2%} {ton_tb * GIA_VON / 1e6:9.1f}")
sd4 = {}
for che_do in ["doc_lap", "theo_tuan"]:
n = nhu_cau(che_do, np.random.default_rng(2026))
sd4[che_do] = np.convolve(n, np.ones(4), "valid").std()
print(f"{che_do}: độ lệch chuẩn 1 ngày {n.std():.2f}, tổng 4 ngày liên tiếp {sd4[che_do]:.2f}")
print("Mục tiêu 95%, khi giả định bị lệch:")
for ten, rop, che_do in [("đặt cuối ngày", 39, "cuoi_ngay"), ("theo tuần", 39, "theo_tuan"),
("theo tuần, sigma đo lại", math.ceil(MU + norm.ppf(0.95) * math.sqrt(
sd4["theo_tuan"] ** 2 + D_TB ** 2 * L_VAR)), "theo_tuan")]:
csl, ck, het, mat, fr, ton_tb = mo_phong(rop, che_do)
print(f" {ten:<24} rop {rop}: csl_mp {csl:6.2%}, het/nam {het:4.1f}, mat/nam {mat:5.1f}, fr_mp {fr:6.2%}")
Kết quả trên laptop Intel Core Ultra 5 125U, Windows 11, chạy hết khoảng 36 giây. csl_ct là CSL theo công thức chuẩn với ROP đã làm tròn, ck/nam là số chu kỳ mỗi năm, het/nam số chu kỳ có hết hàng, mat/nam số máy bị mất:
Python 3.14.3, numpy 2.4.4, scipy 1.17.1, 1000 năm
mu_DL = 24.0, sigma_L = 0.949, sigma_DL = 8.97, bỏ quên sigma_L: 6.93
muc cach rop csl_ct csl_dung csl_mp ck/nam het/nam mat/nam fr_ct fr_mp von_trieu
0.5 chuan 24 50.00% 56.89% 55.26% 34.4 15.39 128.2 94.04% 94.15% 180.7
0.9 chuan 36 90.96% 90.54% 90.00% 36.2 3.61 22.5 99.37% 98.98% 239.1
0.95 chuan 39 95.28% 94.15% 93.71% 36.3 2.28 13.3 99.71% 99.39% 255.3
0.99 chuan 45 99.04% 98.03% 97.80% 36.5 0.80 4.0 99.95% 99.82% 288.5
0.999 chuan 52 99.91% 99.56% 99.56% 36.5 0.16 0.7 100.00% 99.97% 327.8
0.95 dung 40 96.28% 95.06% 94.66% 36.3 1.94 11.1 99.78% 99.49% 261.0
0.99 dung 49 99.73% 99.14% 99.14% 36.5 0.32 1.6 99.99% 99.93% 311.0
0.999 dung 59 100.00% 99.92% 99.91% 36.5 0.03 0.1 100.00% 99.99% 367.0
doc_lap: độ lệch chuẩn 1 ngày 3.47, tổng 4 ngày liên tiếp 6.94
theo_tuan: độ lệch chuẩn 1 ngày 3.45, tổng 4 ngày liên tiếp 10.11
Mục tiêu 95%, khi giả định bị lệch:
đặt cuối ngày rop 39: csl_mp 88.92%, het/nam 4.0, mat/nam 25.4, fr_mp 98.84%
theo tuần rop 39: csl_mp 86.16%, het/nam 4.9, mat/nam 50.3, fr_mp 97.70%
theo tuần, sigma đo lại rop 44: csl_mp 91.84%, het/nam 2.9, mat/nam 29.3, fr_mp 98.66%
Quy tắc cũ của cửa hàng 58 đạt 55,26%: 15,4 lần hết hàng mỗi năm, khoảng 1,3 lần mỗi tháng, và 128,2 máy bị mất. CSL nhỉnh hơn 50% vì D_L lệch phải: trung vị là 23, và chu kỳ bán đúng 24 máy chưa hết hàng. Fill rate khớp công thức, 94,15% so với 94,04%.
Ở mức cao, mô phỏng thấp hơn mục tiêu: 93,71% cho 95%, 97,80% cho 99%. Cột csl_dung cho thấy chỗ sai nằm ở công thức: phân phối đúng của D_L cũng chỉ cho 94,15% và 98,03%. Nhị thức âm trộn thêm lead time dao động có đuôi phải dày hơn phân phối chuẩn cùng trung bình và phương sai. Ở mục tiêu 99%, công thức hứa (1 − 0,9904) × 36,5 = 0,35 lần hết hàng mỗi năm, mô phỏng đếm 0,80. Ở 99,9%, hứa 0,03, đếm 0,16. Chopra, Reinhardt và Dada dẫn Eppen và Martin (1988) về đúng điểm này: giả định chuẩn có thể cho xác suất hết hàng sai rất xa.
Sửa bằng cách đọc ROP thẳng từ phân vị của phân phối đúng: 40, 49 và 59 máy cho CSL 94,66%, 99,14% và 99,91%. Mô phỏng vẫn thấp hơn csl_dung vài phần mười điểm, vì lần bán kích hoạt phiếu nhập dễ rơi vào ngày đang bán chạy và phần còn lại của ngày đó bán nhanh hơn trung bình. Với dữ liệu thật, phân phối của D_L lấy từ lịch sử: số bán trong từng lead time đã xảy ra. Dòng 90% còn trả lời câu ở mục 2: bỏ quên độ lệch của lead time, mục tiêu 95% cho ROP 36, trùng dòng 90%, với CSL thật 90,00% và 22,5 máy mất mỗi năm thay vì 13,3.
4. Đánh đổi: vốn nằm trong kho
Tồn trên kệ trung bình xấp xỉ SS + Q/2, nên mỗi máy tồn kho an toàn nằm trên kệ quanh năm. Biểu đồ lấy cột von_trieu của các dòng chuan:
Với lãi gộp giả định 890.000 đ mỗi máy (giá bán 6.490.000 đ, minh họa), mỗi bậc giữ lại được bao nhiêu lãi so với vốn phải thêm, tiền tính bằng triệu đồng:
| Bậc | Vốn thêm | Máy mất bớt/năm | Lãi giữ lại/năm | Lãi/vốn |
|---|---|---|---|---|
| 50% → 95% | 74,6 | 114,9 | 102,3 | 137% |
| 95% → 99% | 33,2 | 9,3 | 8,3 | 25% |
| 99% → 99,9% | 39,3 | 3,3 | 2,9 | 7,5% |
Bậc cuối đặt thêm 39,3 triệu đồng lên kệ để giữ 2,9 triệu đồng lãi gộp mỗi năm. Nếu chi phí giữ hàng, gồm chi phí vốn, mặt bằng và mất giá khi hãng ra mẫu mới, vượt 7,5% giá trị hàng mỗi năm thì bậc đó lỗ. Bảng chưa tính khách bỏ đi hẳn sau một lần hết hàng; ai tin phần đó lớn thì cộng vào lãi mất mỗi máy. Nếu 120 cửa hàng có cùng số liệu, đi từ 95% lên 99,9% cho riêng mẫu này giữ thêm khoảng 120 × 72,5 = 8.700 triệu đồng trong kho (ước lượng).
5. Hàng chỉ bán một đợt: bài toán người bán báo
Đợt flash sale SP-00777 nhận 300 máy như con số có sẵn, và bài đó lo cho hệ thống không bán quá 300. Câu hỏi đứng trước là nhập bao nhiêu. Cho đợt 11/11 sắp tới, BanHang mua đứt một lô SP-00865, mẫu đời trước, giá nhập 3.200.000 đ, bán trong đợt 3.990.000 đ. Hãng đã ra đời mới, máy thừa sau đợt bán cho đối tác thanh lý 2.700.000 đ. Dự báo từ các đợt tương tự: trung bình 1.000 máy, độ lệch chuẩn 300 (mọi số là minh họa).
Đây là bài toán người bán báo (newsvendor): một lần nhập, một mùa bán, thừa thì lỗ, thiếu thì mất lãi. Arrow, Harris và Marschak đã xét mô hình tĩnh này trong "Optimal Inventory Policy", Econometrica 1951. Lập luận cận biên: máy thứ Q bán được với xác suất 1 − F(Q) và đem về Cu, không bán được với xác suất F(Q) và mất Co. Nhập thêm còn lời khi Cu·(1 − F(Q)) > Co·F(Q), nên Q tối ưu là chỗ hai vế bằng nhau:
Cu = giá bán − giá nhập = 3.990.000 − 3.200.000 = 790.000 đ (lãi mất khi thiếu một máy)
Co = giá nhập − giá thanh lý = 3.200.000 − 2.700.000 = 500.000 đ (lỗ khi thừa một máy)
F(Q*) = Cu / (Cu + Co) = 790.000 / 1.290.000 = 0,6124 (tỉ số tới hạn)
Q* = μ + z·σ, z = Φ⁻¹(0,6124) = 1.000 + 0,2856 × 300 = 1.085,7 → 1.086 máy
Tại Q*: lời kỳ vọng 0,3876 × 790.000 = 306.204 đ, lỗ kỳ vọng 0,6124 × 500.000 = 306.200 đ
Lợi nhuận kỳ vọng tính bằng hàm mất mát ở mục 2. Số máy bán được kỳ vọng là μ − σ·G(z) = 1.000 − 300 × 0,2723 = 918,3. Lợi nhuận kỳ vọng = (giá bán − giá thanh lý) × 918,3 − Co × Q* = 1.290.000 × 918,3 − 500.000 × 1.085,7 ≈ 641,8 triệu đồng. Script kiểm cả hai trên 200.000 kịch bản nhu cầu, hạt giống 1111: lợi nhuận trung bình cho từng Q từ 900 đến 1.300, Q cho lợi nhuận lớn nhất, và phân vị 0,6124 của chính 200.000 kịch bản đó.
import numpy as np
from scipy.stats import norm
GIA_BAN, GIA_NHAP, GIA_THANH_LY = 3_990_000, 3_200_000, 2_700_000
MU, SD = 1000, 300 # dự báo nhu cầu cả đợt, máy
CU, CO = GIA_BAN - GIA_NHAP, GIA_NHAP - GIA_THANH_LY
TI_SO = CU / (CU + CO)
Z = norm.ppf(TI_SO)
Q_CT = MU + Z * SD
BAN_CT = MU - SD * (norm.pdf(Z) - Z * norm.sf(Z)) # số máy bán được kỳ vọng, công thức
LOI_CT = ((GIA_BAN - GIA_THANH_LY) * BAN_CT - CO * Q_CT) / 1e6
print(f"Cu = {CU:,}, Co = {CO:,}, tỉ số tới hạn = {TI_SO:.4f}, z = {Z:.4f}, Q* = {Q_CT:.1f}, "
f"lợi nhuận kỳ vọng {LOI_CT:.1f} triệu")
rng = np.random.default_rng(1111)
D = np.maximum(rng.normal(MU, SD, 200_000).round(), 0) # 200.000 kịch bản cho một đợt khuyến mãi
def ket_qua(q): # lợi nhuận kỳ vọng (triệu đ), P(hết hàng), máy thừa TB
ban = np.minimum(q, D)
loi = GIA_BAN * ban + GIA_THANH_LY * (q - ban) - GIA_NHAP * q
return loi.mean() / 1e6, (D > q).mean(), (q - ban).mean()
Qs = np.arange(900, 1301) # dò từng máy quanh cực đại
loi = np.array([ket_qua(q)[0] for q in Qs])
print(f"Mô phỏng: Q tốt nhất = {Qs[loi.argmax()]}, phân vị {TI_SO:.4f} của 200.000 kịch bản = {np.quantile(D, TI_SO):.0f}")
print(" Q loi_nhuan_trieu p_het_hang may_thua_tb")
for q in [600, 700, 800, 900, 1000, round(Q_CT), 1200, 1300, 1400, 1500, 1600, 1900]:
ln, p_het, thua = ket_qua(q)
print(f"{q:5d} {ln:16.1f} {p_het:11.1%} {thua:12.1f}")
Kết quả, cùng máy và phiên bản, chạy hết khoảng 9 giây:
Cu = 790,000, Co = 500,000, tỉ số tới hạn = 0.6124, z = 0.2856, Q* = 1085.7, lợi nhuận kỳ vọng 641.8 triệu
Mô phỏng: Q tốt nhất = 1086, phân vị 0.6124 của 200.000 kịch bản = 1086
Q loi_nhuan_trieu p_het_hang may_thua_tb
600 457.7 90.9% 12.6
700 520.9 84.1% 24.9
800 573.9 74.9% 45.1
900 613.2 63.2% 75.8
1000 636.4 50.1% 119.0
1086 642.7 38.7% 166.9
1200 632.5 25.2% 244.6
1300 608.7 15.8% 324.3
1400 574.5 9.1% 412.0
1500 533.2 4.8% 505.2
1600 487.6 2.2% 601.8
1900 340.7 0.1% 899.5
Lợi nhuận kỳ vọng cực đại ở 1.086 máy, đúng phân vị 0,6124 của nhu cầu
Lợi nhuận kỳ vọng (triệu đ)
Số máy nhập
Bảng số liệu
| Số máy nhập | Mô phỏng 200.000 kịch bản |
|---|---|
| 600 | 457,7 triệu đ |
| 700 | 520,9 triệu đ |
| 800 | 573,9 triệu đ |
| 900 | 613,2 triệu đ |
| 1.000 | 636,4 triệu đ |
| 1.086 | 642,7 triệu đ |
| 1.200 | 632,5 triệu đ |
| 1.300 | 608,7 triệu đ |
| 1.400 | 574,5 triệu đ |
| 1.500 | 533,2 triệu đ |
| 1.600 | 487,6 triệu đ |
| 1.900 | 340,7 triệu đ |
Mô phỏng, phân vị mẫu và công thức cùng ra 1.086. Lợi nhuận mô phỏng 642,7 triệu đồng lệch công thức 0,9 triệu, chừng 0,14%, do mẫu ngẫu nhiên và nhu cầu làm tròn thành số nguyên. Ở Q tối ưu, đợt khuyến mãi hết hàng với xác suất 38,7%: khoảng bốn trong mười đợt như thế hết hàng trước khi kết thúc, và đó là kết quả đúng.
Đỉnh bằng, hai sườn dốc. Nhập đúng dự báo trung bình 1.000 máy chỉ mất 6,3 triệu đồng so với tối ưu. Nhập 1.900 máy, trung bình cộng ba độ lệch chuẩn, để "không bao giờ hết hàng" thì mất 302 triệu đồng, gần một nửa lợi nhuận. Giá thanh lý quyết định nhiều: đối tác trả 3.000.000 đ thì Co còn 200.000, tỉ số thành 790/990 = 0,798, z = 0,834 và Q* = 1.250 máy. Mục 4 cũng là phép tính này, lặp lại mỗi chu kỳ: lãi mất khi thiếu một máy đặt cạnh chi phí giữ thêm một máy trên kệ.
6. Khi công thức sai
Nhu cầu không độc lập giữa các ngày. σ_d·√L đúng khi các ngày độc lập; phương sai của tổng còn cộng thêm hai lần tổng các hiệp phương sai. Chế độ theo_tuan giữ nguyên tốc độ mua cả tuần, như khi một mẫu đang được quảng cáo. Từng ngày vẫn phân phối như cũ, độ lệch chuẩn 3,45 so với 3,47, nhưng tổng 4 ngày liên tiếp lệch 10,11 thay vì 6,94. Với ROP 39, CSL rơi xuống 86,16% và số máy mất tăng từ 13,3 lên 50,3 mỗi năm.
Đo độ lệch chuẩn trên tổng 4 ngày thay vì nhân √4 cho ROP 44 và 91,84%. Phần còn thiếu đến từ việc phiếu nhập hay được tạo trong tuần đang bán chạy, và tuần đó kéo sang lead time. Tài liệu MIT ghi rằng σ_DL đúng ra là sai số dự báo (RMSE) trong lead time; độ lệch chuẩn của nhu cầu chỉ là xấp xỉ khi dự báo bằng trung bình. Hướng đúng là dự báo bám theo tốc độ hiện tại và tính tồn kho an toàn từ sai số của dự báo đó; bài này không mô phỏng phần đó. Mùa vụ cũng vậy: Tết, mùa tựu trường hay ngày hãng ra mẫu mới làm trung bình đổi, và tồn kho an toàn tính quanh trung bình cũ thì z nào cũng sai.
ERP chỉ đặt hàng lúc đóng cửa. Khi job cuối ngày thấy vị thế tồn ≤ 39 thì tồn đã xuống dưới 39 từ trong ngày, và lô hàng chỉ bắt đầu đi từ tối. Cùng ROP 39, chế độ cuoi_ngay chỉ đạt 88,92%. Giữ job cuối ngày thì phải đo phần hụt đó và cộng vào ROP, hoặc mô phỏng như mục 3 với đúng lịch của job.
Hàng bán chậm. Một mẫu cao cấp bán 0,25 máy mỗi ngày, lead time 4 ngày cố định: D_L trung bình 1 máy, chỉ vài giá trị nguyên và lệch hẳn sang phải. Tài liệu MIT dùng Poisson cho hàng bán chậm. So ROP của công thức chuẩn, làm tròn tới số nguyên gần nhất, với phân vị Poisson:
from scipy.stats import norm, poisson
mu = 0.25 * 4 # 0,25 máy/ngày, lead time 4 ngày: trung bình 1 máy
print("muc rop_chuan lam_tron P(D<=rop) P(het) rop_poisson")
for muc in [0.90, 0.95, 0.99, 0.999]:
rop_chuan = mu + norm.ppf(muc) * mu ** 0.5 # sigma của Poisson = căn trung bình
rop = round(rop_chuan)
print(f"{muc:<6} {rop_chuan:9.2f} {rop:9d} {poisson.cdf(rop, mu):10.4f} {poisson.sf(rop, mu):7.2%}"
f" {poisson.ppf(muc, mu):12.0f}")
muc rop_chuan lam_tron P(D<=rop) P(het) rop_poisson
0.9 2.28 2 0.9197 8.03% 2
0.95 2.64 3 0.9810 1.90% 3
0.99 3.33 3 0.9810 1.90% 4
0.999 4.09 4 0.9963 0.37% 5
Mục tiêu 99% cho 3,33, làm tròn thành 3, xác suất hết hàng mỗi chu kỳ 1,90%, gần gấp đôi mục tiêu. Mục tiêu 99,9% làm tròn 4,09 thành 4 cho 0,37%, gấp 3,7 lần. Mỗi máy ở đây là một bước nhảy vài điểm phần trăm, nên đọc ROP thẳng từ poisson.ppf, hoặc nbinom.ppf khi phương sai lớn hơn trung bình.
Mức phục vụ thấp. Theo công thức chuẩn, giảm σ_L luôn giảm ROP khi CSL trên 50%. Chopra, Reinhardt và Dada (2004) chỉ ra rằng với phân phối đúng có một ngưỡng trên 50%, trong các ví dụ của họ khoảng 50–70%, mà dưới đó giảm σ_L lại làm ROP tăng, và rút ngắn chính lead time mới là đòn bẩy đúng. Cửa hàng chạy CSL thấp như quy tắc cũ nên tính bằng phân phối đúng trước khi chọn giảm σ_L hay giảm L.
Những chỗ hay hiểu sai
- "Tồn đủ bán đúng thời gian giao là an toàn." Đó là ROP bằng trung bình: 15,4 lần hết hàng mỗi năm. Phần an toàn là SS nằm trên trung bình.
- "Mức phục vụ 95% nghĩa là 95% khách mua được hàng." CSL đếm chu kỳ, fill rate đếm khách: CSL 55,26% đi cùng fill rate 94,15%.
- "Độ lệch trong lead time là độ lệch một ngày nhân √L." Chỉ khi lead time cố định và các ngày độc lập. Quên σ_L còn 90%, nhu cầu theo tuần còn 86,16%.
- "Công thức z·σ cho đúng mức phục vụ đã chọn." Nhu cầu lệch phải thì mục tiêu 99% chỉ đạt 97,80%. Đọc ROP từ phân vị của phân phối thật hoặc của lịch sử.
- "Hết hàng trong đợt khuyến mãi là dự báo sai." Với Cu = 790.000 và Co = 500.000, nhập tối ưu vẫn hết hàng ở 38,7% số đợt.
Đọc tiếp
- Flash sale bán quá tồn kho: sau khi chốt số máy nhập, giữ cho hệ thống không bán quá số đó khi 2.000 khách tranh 300 máy.
- Transaction, khóa và isolation: trừ kho đúng dưới tải đồng thời, điều kiện để vị thế tồn ở mục 1 là số thật.
- SLO thanh toán và cảnh báo burn rate: cùng câu hỏi mỗi số 9 thêm vào đáng giá bao nhiêu, áp cho lệnh thanh toán thay vì chu kỳ nhập hàng.
Nguồn
- MIT Center for Transportation & Logistics, CTL.SC1x Supply Chain Fundamentals, Key Concepts, bản V4 Spring 2017: CSL, item fill rate và G(k), s = μ_DL + k·σ_DL, σ_DL khi lead time dao động, σ_DL là RMSE của dự báo, Poisson cho hàng bán chậm, tỉ số tới hạn có giá thanh lý.
- S. Chopra, G. Reinhardt, M. Dada, The Effect of Lead Time Uncertainty on Safety Stocks, Decision Sciences 35(1), 2004, trang 1–24: σ của nhu cầu trong lead time theo Silver và Peterson (1985), trích Eppen và Martin (1988), CSL so với fill rate, ngưỡng mà dưới đó giảm σ_L làm tăng ROP.
- J. E. Tyworth, Single-Period Inventory System, technical note, Penn State, 2014: Cu, Co có giá thanh lý, tỉ số tới hạn và lập luận cận biên.
- K. J. Arrow, T. Harris, J. Marschak, Optimal Inventory Policy, Econometrica 19(3), 1951, trang 250–272.
- Wikipedia, Newsvendor model và Negative binomial distribution: lịch sử bài toán; hỗn hợp gamma–Poisson với phương sai λ(1 + λ/r).
- SciPy, scipy.stats.nbinom: tham số n, p và hàm khối xác suất.