Thư viện / Kiến thức
Phân bổ Item Charge Business Central: phần lẻ ở đâu, vì sao trả hàng bị đảo
Phân bổ item charge trong Business Central chia từng dòng trên phần còn lại nên lệch tiền rơi vào dòng cuối. Trả hàng có charge âm thì tỷ lệ bị đảo, và cách sửa.
Phân bổ item charge trong Business Central bằng nút Suggest Item Charge Assignment chạy lần lượt từng dòng theo Line No.: mỗi dòng lấy tỷ lệ của mình nhân với phần charge còn lại, rồi trừ đi. Code không có bước dồn phần lẻ. Lệch số lượng nằm ở dòng phát sinh ra nó, lệch tiền rơi vào dòng cuối. Return Order tạo bằng Get Posted Document Lines to Reverse thì đi một công thức khác, và với charge chiết khấu (đơn giá âm), công thức đó đảo ngược tỷ lệ giữa các mặt hàng.
Tôi đọc code Base Application 28.1 (codeunit Item Charge Assgnt. (Sales) và Copy Document Mgt.) rồi tính lại khớp từng số trên màn hình của một khách hàng ngành thực phẩm và đồ uống. Số trong bài là số tự dựng theo đúng công thức đó, viết theo định dạng BC: dấu phẩy ngăn hàng nghìn, dấu chấm thập phân.
Tài liệu gốc của Microsoft: Use item charges to account for extra trade costs.
Suggest Item Charge Assignment lấy số nào để chia#
Trên trang Item Charge Assignment (Sales), nút Suggest Item Charge Assignment gọi hàm SuggestAssignment với hai tổng: TotalQtyToAssign là Quantity của dòng Charge (Item), TotalAmtToAssign là số tiền của dòng đó. Cả hai là số của dòng charge, không phải của dòng hàng. Dòng charge có Quantity 1 thì lúc bắt đầu, phần còn lại là 1 và nguyên số tiền.
Có từ hai dòng hàng trở lên thì BC hiện menu Equally, By Amount, By Weight, By Volume. Trước khi chia, bốn cột Qty. to Assign, Amount to Assign, Qty. to Handle, Amount to Handle bị đặt về 0, số gõ tay trước đó mất hết.
Hai tham số làm tròn:
- Số lượng:
UOMMgt.QtyRndPrecision(), bằng 0.00001. - Tiền:
Currency."Amount Rounding Precision". Chứng từ không có Currency Code thì BC lấy field Amount Rounding Precision trong General Ledger Setup. Code không ghi cứng số 1, ví dụ dưới đây giả định setup đang để 1.
Phần lẻ rơi vào dòng nào: chia 300,000 cho ba dòng bằng nhau#
Hàm AssignByAmount lặp theo Line No. Mỗi vòng làm ba việc:
Qty. to Assign= Round(Line Amount dòng này / tổng Line Amount còn lại × Qty còn lại, 0.00001)Amount to Assign= Round(Qty vừa tính / Qty còn lại × Amount còn lại, Amount Rounding Precision)- Trừ Qty, Amount, Line Amount vừa dùng ra khỏi phần còn lại.
Ví dụ: dòng charge Quantity 1, số tiền 300,000. Ba dòng hàng, Line Amount mỗi dòng 100,000.
| Dòng | Qty còn lại | Amount còn lại | Qty thô | Qty. to Assign | Amount to Assign |
|---|---|---|---|---|---|
| 1 | 1.00000 | 300,000 | 1 / 3 = 0.333333 | 0.33333 | 99,999 |
| 2 | 0.66667 | 200,001 | 0.66667 / 2 = 0.333335 | 0.33334 | 100,002 |
| 3 | 0.33333 | 99,999 | 0.33333 | 0.33333 | 99,999 |
Không dòng nào nhận đúng 100,000, vì tiền tính theo Qty đã làm tròn 5 số lẻ (0.33333 × 300,000 = 99,999). Dòng 2 nhận 0.33334 vì tới lượt nó, phần còn lại là 0.66667, chia đôi ra 0.333335, và Round làm tròn nửa lên. Đổi thứ tự dòng thì dòng đứng thứ hai sẽ là dòng nhận 0.33334. Dòng cuối luôn nhận trọn số tiền còn lại, vì Qty của nó bằng đúng Qty còn lại, nên tổng luôn khớp 300,000.
Line Amount không đều cũng chạy y như vậy. Ba dòng 100,000, 200,000 và 300,000 nhận charge 300,000 thì ra 50,001, 99,999 và 150,000.
Cơ sở của By Amount tùy loại dòng được gán, tức field Applies-to Doc. Type:
| Applies-to Doc. Type | Cơ sở phân bổ |
|---|---|
| Order, Invoice, Return Order, Quote, Credit Memo | Abs(Sales Line."Line Amount") |
| Shipment | Item Charge Base Amount trên Sales Shipment Line |
| Return Receipt | Item Charge Base Amount trên Return Receipt Line |
Dòng có Line Amount bằng 0 bị loại khỏi vòng chia. Equally chạy cùng cơ chế trừ dần, tỷ lệ là 1 chia số dòng còn lại. By Weight và By Volume khác hẳn: phần dư số lượng và tiền sau khi làm tròn được cộng sang dòng kế tiếp, Amount tính bằng Qty × Unit Cost cộng phần dư.
Vì sao Return Order phân bổ ngược so với Sales Order#
Bấm Get Posted Document Lines to Reverse trên Return Order thì BC không gọi AssignByAmount. Nó gọi CopyFromSalesLineItemChargeAssign trong codeunit Copy Document Mgt.. Hàm này đọc Value Entry của charge trên hóa đơn gốc, gán cứng Applies-to Doc. Type = Shipment, rồi tính số lượng cho từng dòng phiếu xuất:
QtyToAssign :=
Sign(ToSalesLine.Quantity) * Abs(ValueEntry."Sales Amount (Actual)") * CurrencyFactor / ToSalesLine."Unit Price";
Sau đó code bù làm tròn hai lượt, chú thích trong code ghi "Use 2 passes to correct rounding issues". Lượt một lấy phần còn thiếu so với Quantity của dòng charge, chia đều cho số Value Entry, cộng vào mọi dòng. Lượt hai dồn chênh lệch cuối cùng vào dòng cuối.
Công thức này vướng hai lỗi chồng lên nhau:
- Lỗi dấu: charge chiết khấu có
Unit Priceâm. Tử số cóAbs, mẫu số thì không, nên Qty thô của mọi dòng ra số âm. Phần bù gần bằng 1 cộng đều vào các dòng, dòng âm nhiều hơn lại thành dòng nhỏ hơn. - Lỗi lệch VAT:
Sales Amount (Actual)là số chưa thuế, cònUnit Priceđã gồm thuế khi bật Prices Including VAT. Với thuế 8%, tổng Qty thô chỉ đạt khoảng 1 / 1.08, phần thiếu chia đều kéo tỷ lệ các dòng về gần nhau.
Ví dụ: Sales Order có dòng charge chiết khấu Quantity 1, Unit Price -540,000 đã gồm VAT 8%, tức -500,000 chưa thuế. Phân bổ By Amount cho hai mặt hàng A và B có Line Amount tỷ lệ 1 : 2. Lúc post hóa đơn, Value Entry của charge đúng tỷ lệ. Sau đó tạo Return Order bằng Get Posted Document Lines to Reverse:
| Bước | Dòng Shipment của A | Dòng Shipment của B |
|---|---|---|
| Value Entry của charge lúc bán | -166,665 (33.33%) | -333,335 (66.67%) |
| Qty thô, chia cho Unit Price -540,000 | -0.3086389 | -0.6172870 |
| Phần bù, (1 - (-0.9259259)) / 2 | +0.9629630 | +0.9629630 |
Qty. to Assign sau khi làm tròn | 0.65432 | 0.34568 |
Amount to Assign BC tự tính theo Qty | -327,160 | -172,840 |
Post nguyên trạng thì mặt hàng A, lúc bán chịu 166,665 chiết khấu, được hoàn 327,160. Mặt hàng B lúc bán chịu 333,335, chỉ được hoàn 172,840. Doanh thu thuần theo từng mặt hàng sai sau lần trả hàng. Trên đơn thật của khách, màn hình ra 0.65433 và 0.34567, cùng một kiểu đảo.
Tính lại theo công thức trên với ba kịch bản để tách hai lỗi:
| Kịch bản | Dòng A | Dòng B | Kết quả |
|---|---|---|---|
| Đơn giá âm, bật Prices Including VAT | 0.65432 | 0.34568 | Đảo thứ tự và méo tỷ lệ |
| Đơn giá âm, tắt Prices Including VAT | 0.66667 | 0.33333 | Vẫn đảo thứ tự |
| Đơn giá dương, bật Prices Including VAT | 0.34568 | 0.65432 | Đúng thứ tự, lệch tỷ lệ |
Đoạn code này giống nhau ở bản 28.0 và 28.1, các bản sau tôi chưa thử. Không extension nào đang cài ở dự án đụng bảng Item Charge Assignment (Sales), nên đây là hành vi của bản chuẩn.
Sửa trên Return Order trước khi post#
Get Posted Document Lines to Reverse chọn đích gán đúng, chỉ sai tỷ lệ. Dòng Shipment post qua PostItemChargePerShpt trong codeunit Sales-Post, bám vào Item Ledger Entry của lần xuất cũ và khử đúng khoản chiết khấu đã ghi lúc bán. Dòng Return Order post qua PostItemChargePerOrder, bám vào Item Ledger Entry mới sinh ra lúc nhận hàng về. Hoàn chiết khấu của lần bán gốc thì giữ dòng Shipment.
Cách sửa:
- Sau khi bấm Get Posted Document Lines to Reverse, mở Item Charge Assignment của dòng charge.
- Gõ
Qty. to Assigntrên các dòng Shipment theo đúng tỷ lệ lúc bán, ở ví dụ trên là 0.33333 cho A và 0.66667 cho B. Amount tự tính lại theo Qty (hàmSuggestAssignmentFromLinechạy khi validate) và ra -166,665, -333,335, khớp Value Entry lúc bán. - Để nguyên các dòng Return Order ở 0. Đừng bấm Suggest Item Charge Assignment, vì lệnh đó chia cả cho các dòng Return Order.
- Rà các Posted Return Receipt và Posted Sales Credit Memo cũ có dòng charge đơn giá âm, để tìm những đơn đã bị hoàn sai.
Một bẫy khi đọc dữ liệu: kho bật Require Receive thì Return Qty. to Receive trên dòng hàng bằng 0 cho tới khi post Warehouse Receipt. Số 0 đó không có nghĩa là không có hàng trả về.
Có một việc tôi chưa thử: sau khi post Warehouse Receipt rồi mới invoice Return Order, BC có đụng lại bảng assignment hay không. Làm theo cách trên thì nên mở lại Item Charge Assignment, xem lại hai số trước khi post hóa đơn.
Sửa gốc bằng extension#
Microsoft để sẵn event OnBeforeCopyFromSalesLineItemChargeAssign trong Copy Document Mgt., có tham số IsHandled. Subscriber đặt IsHandled := true rồi tự tính lại, với hai chỗ khác bản gốc: lấy Abs cho cả mẫu số, và bù phần thiếu theo tỷ lệ (hoặc đưa tử số và mẫu số về cùng gốc thuế trước khi chia). Hàm này chỉ chạy lúc bấm Get Posted Document Lines to Reverse, không nằm trên đường post. Lỗi này đã được báo cho Microsoft hay chưa thì tôi chưa kiểm.
Item Charge làm chiết khấu: doanh thu thuần phải cộng cả dòng charge#
Khi dùng Item Charge để giảm trừ doanh thu cho từng mặt hàng, BC không sửa Sales Amount (Actual) của dòng hàng. Nó sinh một Value Entry riêng mang cùng Item No., có Item Charge No. khác rỗng, Sales Amount (Actual) âm, Item Ledger Entry Quantity = 0 và Invoiced Quantity = 0.
Báo cáo doanh thu thuần theo mặt hàng đọc từ Value Entry vì vậy phải cộng cả dòng này. Hai lỗi hay gặp khi viết báo cáo:
- Lọc bỏ dòng có
Invoiced Quantity= 0 thì mất toàn bộ phần chiết khấu. - Lấy
Valued Quantitylàm số lượng thì sản lượng bị đếm thêm, vì dòng charge vẫn mang số lượng của dòng hàng mà nó bám vào.