Skip to content

Pricing and invoice recipes

Start with the runnable invoice creation example. The Rust fragments below replace its line item or extend its builder. Use the advanced binding methods for the corresponding operations in other languages.

Price per pack and a line discount

A price base expresses how much quantity the quoted price covers. Here, SAR 100 buys ten units. Selling 25 units gives SAR 250 before a SAR 10 line allowance: line net is SAR 240, VAT is SAR 36, and line gross is SAR 276.

Replace the example's let item = ... with:

use fatoora_core::invoice::Adjustment;

let item = LineItem::builder()
    .item_name("Notebook")
    .invoiced_quantity("25".parse()?)
    .invoiced_quantity_unit_code("PCE")
    .item_net_price("100".parse()?)
    .item_price_base_quantity("10".parse()?)
    .vat_category(VatCategory::Standard)
    .vat_rate("15".parse()?)
    .allowance(Adjustment::amount("10".parse()?).reason("Volume discount"))
    .build()?;

Use .charge(...) for a line surcharge. An Adjustment::percentage(base, percent) calculates an adjustment using the supplied base. For example, a 10% allowance on SAR 250 is Adjustment::percentage("250".parse()?, "10".parse()?).

For gross pricing, supply .item_gross_price(...) and .item_price_discount(...). The builder derives the net price and checks it if you also supply .item_net_price(...). Price-base division retains exact rational precision until the line net is rounded at the calculation boundary.

Document discounts and charges

Each document adjustment carries its VAT category and rate. To subtract SAR 10 from the creation example's SAR 100 taxable amount, add this call before .build():

.allowance(fatoora_core::invoice::DocumentAdjustment::new(
    fatoora_core::invoice::Adjustment::amount("10".parse()?).reason("Loyalty discount"),
    VatCategory::Standard,
    "15".parse()?,
))

The resulting VAT is SAR 13.50 and payable is SAR 103.50. Use .charge(...) for shipping or another document charge with its own tax treatment. Repeated calls append adjustments.

The scalar invoice_level_discount and invoice_level_charge setters remain available for a single VAT group. Use independent adjustment entries when you need multiple groups; combining the two forms is rejected.

Mixed VAT categories and rates

Assign each line its category and rate. The builder groups accepted line net amounts by both values, applies each group's document adjustments, and calculates the VAT breakdown. Document VAT is the sum of breakdown taxes.

For example, SAR 100 at 15% plus SAR 50 at 0% gives SAR 15 VAT and SAR 165 payable before other adjustments. Use VatCategory::Zero for the zero-rated line. Exempt and OutOfScope are also available; those categories use a zero rate.

Set exemption metadata where the invoice's tax treatment requires it. A line builder accepts .exemption(TaxExemption { reason_code, reason }); document breakdown metadata uses .tax_exemption(category, rate, exemption) on the invoice builder. Both fields are optional strings in the model. Choose the applicable code and explanation for the transaction, then run local business validation.

Credit and debit notes

Use InvoiceType::CreditNote(InvoiceSubType::Simplified) or InvoiceType::DebitNote(InvoiceSubType::Simplified) in place of the tax-invoice type. Both also support Standard. Add the original invoice reference and a payment instruction explaining the correction:

.billing_reference(fatoora_core::invoice::OriginalInvoiceRef::new("INV-1001"))
.payment_instruction("Return of goods")

Keep line quantities and amounts positive. The document type identifies the correction. Set a new ID, UUID, issue timestamp, counter, and predecessor hash for the note. Repeated .billing_reference(...) calls preserve multiple references.

Prepayments and final payable amounts

Use InvoiceType::Prepayment(subtype) to create a prepayment invoice. On an invoice that deducts an advance payment, .prepaid_amount(...) subtracts the supplied amount from the tax-inclusive total. .payable_rounding_amount(...) applies a signed rounding adjustment:

Payable = tax-inclusive total − prepaid amount + payable rounding

A SAR 115 tax-inclusive total with SAR 23 prepaid has SAR 92 payable. This amount also supplies QR Tag 4.

To represent an advance payment of SAR 20 plus SAR 3 VAT, insert this declaration before let invoice = ... in the creation example:

use fatoora_core::invoice::{InvoiceTime, OriginalInvoiceRef, VatBreakdown};

let advance = LineItem::builder()
    .item_name("Advance payment adjustment")
    .invoiced_quantity("0".parse()?)
    .invoiced_quantity_unit_code("PCE")
    .item_net_price("0".parse()?)
    .vat_category(VatCategory::Standard)
    .vat_rate("15".parse()?)
    .document_reference(
        OriginalInvoiceRef::new("PRE-1001")
            .with_uuid("b2a43c49-3aab-4e3b-9d67-0da45a5e33cc")
            .with_issue_date_str("2026-10-25")?
            .with_issue_time(InvoiceTime::parse("09:00:00Z")?)
            .with_document_type_code("386"),
    )
    .tax_subtotal(VatBreakdown::new(
        VatCategory::Standard,
        "15".parse()?,
        "20".parse()?,
        "3".parse()?,
    )?)
    .build()?;

Keep the sale's .line_item(item) call and add these calls before the invoice's .build():

.line_item(advance)
.prepaid_amount("23".parse()?)

The result has SAR 15 document VAT and SAR 92 payable. Replace the sample reference with the prepayment invoice's actual identity and issue date/time.

with_document_type_code("386") marks the reference as a prepayment. Without it, the model treats the line as an ordinary sale. Adjustment lines must have zero quantity, price, line net, and line VAT; the advance amounts belong in their VAT subtotals. Their combined taxable and tax subtotals must equal prepaid_amount. Setting that amount alone does not create references or adjustment lines. See the model contract for the calculation checks.

Purchase orders, delivery and payment data

Application data Rust builder API
Purchase order or contract purchase_order_id(...), contract_id(...)
Actual/latest delivery dates delivery(Delivery { actual_date, latest_date })
Payment code with instructions and account payment_means(PaymentMeans::new(code).instruction_note(text).payee_financial_account(account))
Additional party identifiers seller_identifier(...), buyer_identifier(...)
Item identifiers Line builder item_seller_identifier(...), item_buyer_identifier(...), item_standard_identifier(...)
Notes in different languages Repeated note(InvoiceNote::new(language, text)) calls

Use collection getters when reading repeated values; convenience getters expose the first entry. XML import preserves the supported fields, subject to the documented normalization and unsupported-content policy.