FE: module stock — tồn kho #
Phần chung (khởi tạo client, lỗi, danh sách, ngôn ngữ) ở tài liệu FE của Core:
docs/fe/common.md; báo cáo, xuất / nhập, realtime: docs/fe/cms.md (bản đọc:
xforge-documentation, ADR-0102 của xforge-core). Sửa file này CÙNG PR khi đổi
hành vi FE thấy được của module.
Chỉ có ở CMS (XforgeClient). Menu: key stock.levels (quyền stock.read).
Số lượng là CHUỖI theo ĐƠN VỊ GỐC của sản phẩm, ≤ 4 chữ số lẻ, dạng gọn ("12",
"2.5"). Đừng đổi sang Number để cộng trừ; đơn vị bán thêm do chứng từ quy
đổi trước khi vào kho.
Số tồn #
listStockLevels, getStockLevel: mỗi dòng là một (kho, biến thể), mặc định ít
hàng khả dụng trước. Lọc warehouse_id, variant_id, location_id, is_low,
available.lte / .gt.
onHandlà hàng thực có,reservedlà phần đã hứa,available = onHand − reserved.availableâm là đang bán vượt (xem FE củapre_order_setting) hoặc kiểm kê thấy thiếu.updateStockLevel(quyềnstock.adjust): chỉ đặtlowStockThreshold(null= tắt) vàlocationId. Số lượng không đổi ở đây.
Số tồn chỉ đổi qua nhập / điều chỉnh / chứng từ. Mỗi lần đổi onHand sinh phiếu
tự động (PN-… nhập, PX-… xuất), xem ở listStockVouchers /
getStockVoucher; sổ cái ở listStockMovements (lọc reference_type +
reference_id để xem biến động của một chứng từ).
Ghi #
| Thao tác | Quyền | Ghi chú |
|---|---|---|
createStockReceipt | stock.receive | Nhập tay, trả về phiếu PN-… |
createStockAdjustment | stock.adjust | qtyDelta có dấu, reason bắt buộc; trả vouchers (tối đa mỗi chiều một) |
createStockSupplierReturn | stock.receive | Trả nhà cung cấp, trả về phiếu PX-… |
setStockTracking | stock.manage_tracking | Đổi chế độ none / batch / serial |
previewStockAllocation | stock.read | Gợi ý kho xuất theo priority, chỉ đọc; shortfall là phần thiếu |
Cả request trong một transaction, tối đa 200 dòng. Lỗi của một dòng mang
error.at = lines.<i>, i là chỉ số trong body gửi lên: tô đúng dòng đó.
Thiếu hàng. Giảm onHand bị chặn khi tụt dưới 0 hoặc dưới phần đã giữ chỗ:
- 409
stock_insufficient_on_hand(details.onHand); - 409
stock_exceeds_reserved; - 409
stock_insufficient_available(details.available), 409stock_insufficient_batch_available(details.available: chỉ tính lô còn hạn) ở thao tác hứa thêm của chứng từ khác (bán, chuyển kho).
Điều chỉnh gửi allowDeficit: true thì không bị chặn: chỉ cho người dùng chọn
khi đếm thật thấy thiếu, có xác nhận rõ ràng, vì tồn có thể âm.
Lô / serial #
getStockTracking trả chế độ của biến thể. Đổi chế độ chỉ khi không còn tồn /
giữ chỗ / hàng đang chuyển ở mọi kho (409 stock_tracking_locked).
| Chế độ | Nhập | Điều chỉnh, trả nhà cung cấp |
|---|---|---|
none | Gửi kèm lô / serial → 400 stock_not_tracked | Như trên |
batch | lotNumber bắt buộc (400 stock_lot_number_required), hạn dùng tuỳ chọn; lô đã có thì cộng vào | Điều chỉnh: batchId (400 stock_batch_or_serial_required). Trả: lotNumber (404 stock_batch_not_found) |
serial | serialNumbers, mỗi đơn vị một số (400 stock_serial_numbers_required; trùng → 409 stock_serial_exists, details.serialNo) | Điều chỉnh: serialId, ±1 (400 stock_serial_adjust_unit). Trả: serialNumbers (404 stock_serial_not_found) |
Danh sách: listStockBatches (on_hand.gt=0 ẩn lô rỗng),
listExpiringStockBatches (days, expiryStatus: expired, critical,
warning), listStockSerials (status). Lô hết hạn vẫn nằm trong onHand
nhưng không được giữ chỗ / xuất. status của serial có thể thêm giá trị: giá
trị lạ thì hiện nguyên.
Giá vốn (stock.view_cost) #
Không có quyền thì unitCost đọc ra null ở mọi nơi (sổ cái, phiếu, lô), kể cả
khi có giá trị thật: ẩn cột theo quyền, đừng hiện "0" hay "chưa có". Gửi
unitCost khác null mà thiếu quyền → 403 field_write_forbidden, nên ẩn luôn ô
nhập. currency vắng = tiền của store; nhập lô đã có giá vốn bằng tiền khác →
409 stock_batch_currency_mismatch.
Báo cáo #
Chạy qua màn báo cáo chung (listReports, runReport). Quyền stock.read;
thiếu quyền thì báo cáo không có trong danh sách và runReport trả 404. Tham số
warehouse là MÃ kho (không phải id), bỏ trống = mọi kho.
stock.on_hand: tồn theo (kho, biến thể), bỏ dòng cóonHandvàreservedđều bằng 0. Có dòng tổng.unitCost+stockValue(cả ở dòng tổng) chỉ có vớistock.view_cost; thiếu quyền thìnull.stock.low_stock: tham sốthresholdtuỳ chọn. Có thì lấy dòngavailable ≤ threshold; bỏ trống thì dùng ngưỡng riêng của từng dòng (isLow). Cộtthresholdcho biết ngưỡng đã áp. Không gồm kho ngừng hoạt động, biến thể ngừng bán, sản phẩm lưu trữ.
unitCost là giá vốn bình quân gia quyền của mọi lần nhập có giá vốn của biến
thể (cả store, theo tiền của store): chuyển kho không mang giá vốn theo nên
không tính theo từng kho. Đây là số tham khảo, biến thể chưa nhập có giá vốn thì
null.
Tập quá 10 000 dòng → 400 report_too_large: yêu cầu lọc theo kho.
Tồn đầu kỳ: dataset stock.opening_balance #
Qua màn xuất / nhập chung (listDatasets, exportDataset, importDataset).
Xuất: quyền stock.read; nhập: quyền stock.receive.
| Cột | Nhập | Ghi chú |
|---|---|---|
warehouseCode | Bắt buộc | Mã kho |
sku | Bắt buộc | SKU của biến thể |
qty | Bắt buộc | ≥ 0; 0 = bỏ qua dòng |
unitCost | Tuỳ chọn | Chỉ người có stock.view_cost; xuất ra trống nếu thiếu quyền |
| Còn lại | Bỏ qua | productName, reserved, available, tracking: chỉ xuất |
Mỗi (kho, biến thể) chỉ nhập đầu kỳ MỘT lần: nhập lại cùng file không cộng hai
lần, dòng đã nhập tính vào skipped kể cả khi qty đổi. Muốn sửa số thì dùng
điều chỉnh. Nhập thành công sinh phiếu PN-…, mỗi kho một phiếu; sổ cái lọc
được bằng reference_type=stock.opening_balance.
Tối đa 2 000 dòng mỗi file (stock_opening_balance_too_many_rows), vì cả file
giữ bộ đếm phiếu nhập của store tới lúc xong. Nhiều hơn thì chia file.
Dòng lỗi huỷ cả file. Mỗi lỗi có line, column, rule, code, message (đã
dịch); KHÔNG có details, nên hiện message, rẽ nhánh theo code:
stock_opening_balance_tracked: biến thể theo lô / serial không nhập được bằng dataset này vì file không có số lô, hạn dùng, số serial. Hãy nhập bằngcreateStockReceipt, rồi bỏ dòng đó khỏi file;stock_opening_balance_has_stock: dòng tồn đang có hàng từ đường khác, cộng thêm sẽ đếm hai lần. Gặp khi nhập lại file vừa xuất: đó là file để xem, không phải để nhập lại. Dùng điều chỉnh;stock_opening_balance_duplicate: cùng kho + SKU hai lần;stock_opening_balance_cost_forbidden: file có giá vốn mà thiếustock.view_cost;stock_warehouse_not_found,stock_variant_not_found: sai mã kho / SKU.
Trong lúc nhập, các lần nhập kho khác của store phải chờ tới khi file xong: file
lớn nên chạy nền (startDatasetImport) và ngoài giờ cao điểm.
Cảnh báo sắp hết hàng #
Ngưỡng đặt trên từng dòng tồn (lowStockThreshold, hoặc gửi kèm khi nhập). Dòng
không đặt ngưỡng thì không bao giờ cảnh báo. isLow nghĩa là
available ≤ ngưỡng (gồm cả bằng).
Bật ở cấu hình stock.low_stock_alert { enabled } (mặc định false, cấp
account hoặc store, qua màn cấu hình chung). Khi bật, mỗi 7 giờ sáng giờ của
store:
- thông báo
stock.low_stocktới người cóstock.read, có Web Push.params.countlà số biến thể dưới ngưỡng,link.key = stock.levels: mở màn tồn kho vớiis_low=true. Không có gì thì không gửi; không gửi từng dòng; - sự kiện realtime
stock.low_stock,datarỗng: màn tồn kho đang mở thì đọc lại.
count không tính kho ngừng hoạt động, biến thể ngừng bán, sản phẩm lưu trữ,
nên có thể nhỏ hơn số dòng is_low=true trong danh sách.
Màn hình dựng sẵn #
listMenu() → mục stock.levels có screen (type: "crud"): CMS chung vẽ
được từ getMenuScreen({ params: { key: "stock.levels" } }) mà không cần code
riêng (tài liệu FE của Core: cms.md §3.1). Có: danh sách tồn (kho, sản phẩm,
SKU, vị trí, chế độ theo dõi, tồn thực / đã giữ / có thể bán, ngưỡng thấp; sắp
theo có thể bán; lọc kho / biến thể / tồn thấp), sửa ngưỡng tồn thấp.
KHÔNG có (màn tay): đổi vị trí của dòng tồn (locationId chỉ nhận vị trí của
kho của dòng — ô chọn phụ thuộc dữ liệu khác, chưa có trong định dạng), phiếu
nhập / điều chỉnh / trả nhà cung cấp, lô / serial, sổ cái.
Quyền ngoài module mà màn hình dùng (thiếu thì CMS ẩn / rơi về ô nhập thường):
- Lọc theo kho cần
warehouse.read, theo biến thể cầnproduct.read. Sửa ngưỡng tồn thấp làupdateStockLevel— quyềnstock.adjust.