namespace Distribution.Features.Grn; public record LineAmounts(decimal Gross, decimal Discount, decimal Nbt, decimal Vat, decimal Total); /// /// GRN costing. Per line: gross = accepted qty x unit cost; discount = gross x disc%; /// NBT = (gross - discount) x NBT%; VAT = (gross - discount + NBT) x VAT%; /// total = gross - discount + NBT + VAT. Rejected units are never costed. /// Each amount is rounded to 2 dp per line so the GRN totals equal the sum of what the lines show. /// public static class GrnPricing { public static decimal Round(decimal value) => Math.Round(value, 2, MidpointRounding.AwayFromZero); public static LineAmounts Line( int acceptedQty, decimal unitCost, decimal discountPercent, decimal nbtPercent, decimal vatPercent) { var gross = Round(acceptedQty * unitCost); var discount = Round(gross * discountPercent / 100m); var net = gross - discount; var nbt = Round(net * nbtPercent / 100m); var vat = Round((net + nbt) * vatPercent / 100m); return new LineAmounts(gross, discount, nbt, vat, net + nbt + vat); } public static void ValidatePercent(decimal value, string label) { if (value < 0 || value > 100) throw new InvalidOperationException($"{label} must be between 0 and 100."); } public static void ValidateCost(decimal value, string label) { if (value < 0) throw new InvalidOperationException($"{label} cannot be negative."); } }