import type { DiscountCalculationIssue, DiscountDocumentKind, DiscountLineGroup } from "./discount-calculator"; import type { IRoundingConfig } from "../../tax/tax.types"; /** * The record-level discount, applied while a document is being edited. * * A document can carry a header discount -- `PDisc` over its product lines, * `LDisc` over its service lines, and an overall `Disc` over everything -- that * is spread pro rata onto each line as `RecDisc`. Whenever the lines change * (a line added, removed, repriced, its quantity or own discount edited) that * spread has to be redone over the new set of lines, including the one just * added. This does that, finalizes each line's amount (`UnAmt` / `Amt`), and * recalculates each line's `Taxes` on its post-discount amount. * * It replaces `DiscountAndTaxCalculation` and `calculateRecordDiscountForLine` * from `transaction-calculations/discounts-distribution.ts`, covering * everything they did, and differs from them where they were wrong: * * - **The spread is the server's own.** Allocation is `CalculateDiscountProposal`, * the function MainAPI verifies `RecDisc` with, fed exactly as the server feeds * it (`master-prices.ts` `PrepareLines`/`IdText`). So the `RecDisc` the UI * sends is the `RecDisc` the server expects, to the minor unit. The old code * split proportionally in floating point, so the shares did not sum to the * header amount (100 over three equal lines gave 99.99999999999999). * - **Line order is kept.** The old code moved every `NoDisc` line to the end. * - **Stale `RecDisc` is cleared on excluded sale lines.** A line that became `NoDisc`, or a layout row * used to keep its old share and be double-counted. Returns retain the supplied * signed reversal; the return owner verifies it against the original sale. * - **Impossible input is reported, not spread.** A line discount larger than * its amount, or a header discount larger than what remains, used to produce * negative shares. They are now returned as issues and nothing is changed. * - **Nothing is added that was not there.** No `net` scratch field on every * line, and no `Perc`/`LPerc`/`PPerc` written onto documents that lacked them. * - **The input is not mutated.** A new record is returned. * * Header amounts in percent mode. When `Perc` (or `PPerc`/`LPerc`) holds a * positive percentage, the matching amount is recomputed from it over the * current lines -- adding a line makes a 10% discount larger. Otherwise the * amount is fixed and is spread more thinly. This is the documented UI side of * the Disc/Perc convention (docs/common/accounting/numbers-and-discounts.md): * the UI turns a typed percentage into money; the server never does. * * Money that carries more precision than the currency has is rounded half away * from zero to the minor unit, and the rounded value is written back, so the * document sent is the document that was allocated. The server refuses excess * precision rather than rounding it, so leaving it unrounded here would only * move the failure to the save. Taxes follow the same rule, through the same * `CalculateLineTaxAmounts` that `CalculateLineTax` uses: each component on * each line rounded once. Policy: MainAPI * docs/common/accounting/numbers-and-discounts.md#invoice-money-precision. */ export interface RecordDiscountOptions { /** * Which document rules apply. Omit to infer: a document with a returned * line (`Ret: true`) is a sales receipt; everything else follows invoice * rules, which are a superset (items and services, parts/labour/overall * header discounts). Pass it explicitly when the document type is known. */ readonly Kind?: DiscountDocumentKind; /** Minor-unit exponent of the document currency: 2 for INR/USD, 0 for JPY, 3 for KWD. Defaults to 2. */ readonly MinorUnitExponent?: number; /** * Tax codes for recalculating each line's `Taxes`. With none, each line's * existing `Taxes` entries are re-rated from their own `Rate`. The same * input `DiscountAndTaxCalculation` took. A component's `AppliedOn`/`Type` * (tax on tax) is read from here. */ readonly TaxCodes?: readonly unknown[]; /** * The tax rounding in force: the regime's rule with any entity override * (MainUI `GetTaxRounding()`). Only `LineTax` and, with it, `Method`/ * `Precision` affect line tax; see `ResolveLineTaxRounding`. Omit to round * each component to the currency's minor unit. */ readonly TaxRounding?: Partial; } export interface RecordDiscountResult { /** * The updated document: header discount amounts, every line's `RecDisc`, * and the recalculated `Taxes` of each discountable and returned line. * When `Issues` is not empty this is the input, unchanged. */ readonly Record: T; /** * Empty when the discount was applied. Otherwise why it could not be -- * e.g. `HEADER_DISCOUNT_EXCEEDS_CAPACITY` after a line was removed, or * `LINE_DISCOUNT_EXCEEDS_AMOUNT`. `Field` names the offending value. */ readonly Issues: readonly DiscountCalculationIssue[]; } /** How `RecalculateLineTaxes` rates and rounds; the same meanings as in `RecordDiscountOptions`. */ export interface LineTaxOptions { readonly TaxCodes?: readonly unknown[]; readonly TaxRounding?: Partial; /** Minor-unit exponent of the document currency. Defaults to 2. */ readonly MinorUnitExponent?: number; } export interface RecordLineDiscountResult { /** The `RecDisc` this line should carry. Zero when there are issues, or the line is `NoDisc` or a return. */ readonly RecDisc: number; readonly Issues: readonly DiscountCalculationIssue[]; } /** * Spreads the record-level discount over the document's current lines and * recalculates their taxes. Returns a new record; the input is not modified. */ export declare function ApplyRecordDiscountAndTaxes(record: T, options?: RecordDiscountOptions): RecordDiscountResult; /** * The `RecDisc` one line should carry, with the document's other lines as they * are. For a line being added or edited before it is saved: the line is matched * to its existing entry by `_id`, or treated as new and appended when it has no * match. The document itself is not modified. */ export declare function CalculateRecordDiscountForLine(record: object, line: Record, group: DiscountLineGroup, options?: RecordDiscountOptions): RecordLineDiscountResult; /** * Re-rates one line's existing `Taxes[]` on its taxable amount, for a screen * that changes a line outside `ApplyRecordDiscountAndTaxes` (a price or * quantity edit on a document without a record-level discount). The same rule * `ApplyRecordDiscountAndTaxes` applies: `amount` is finalized to the minor * unit, then each component is rounded once. Returns new entries; the input is * not modified. */ export declare function RecalculateLineTaxes(amount: unknown, taxCode: unknown, existingTaxes: unknown, qty?: unknown, options?: LineTaxOptions): unknown[];