---
name: evolution_importquote_tax_drift
description: importQuote tax vs quote — taxSell override can zero a line's GST; per-line recompute now preserves 4dp (reversed from match-Xero)
metadata: 
  node_type: memory
  type: project
  originSessionId: 6b1efdbf-1da0-4547-a2b5-90d8dc872ba8
---

`library/invoice.php::importQuote()` can produce an invoice whose tax differs from the origin quote. As of the high-precision GST project ([[gst_precision_project]]) the original "match Xero / round to 2dp" fix is **REVERSED** — we now preserve 4dp per line and round the header ONCE per tax rate (see [[gst_precision_project]] for the locked method).

**Applied in importQuote** (after the taxSell override + jobInventory qty/price block, inside the non-sections branch): recompute each line at 4dp
```php
DB::UPDATE("update invoiceitems left join tax on tax.id = invoiceitems.taxId
    set invoiceitems.taxAmount = round(cast(invoiceitems.adjPrice as decimal(15,4)) * invoiceitems.qty * (coalesce(tax.value,0)/100), 4)
    where invoiceitems.invoiceid = ?", [$this->invoiceid]);
```
The header tax is NOT `sum(taxAmount)` — it is per-rate `Σ round(rate% × subtotal_of_that_rate, 2)`; this column is line-display only.

**taxSell override (invoice.php:288-290) — FIXED.** Import previously ran `update invoiceitems,inventory set invoiceitems.taxId = inventory.taxSell where itemid=inventory.id` **unconditionally**, so a matched item with `taxSell` `0`/unset made the line **taxId=0 / GST-free even though the quote taxed it at 10%**. Confirmed live on invoice 59861: 9 lines @10% ($11,867,954.75) + 1 line forced to taxId=0 ($1,378.46) → invoice tax $1,186,795.48 vs quote $1,186,933.32 (gap = 1378.46×10% ≈ $137.84). A tax-MAPPING difference, NOT a rounding bug. **Resolution (user):** override now uses a `CASE` — set `taxId = inventory.taxSell` ONLY when the imported quote line's `taxId IS NULL OR = 0`; otherwise keep the quote's taxId (authoritative). `accountid = inventory.incaccount` stays unconditional. Watch: the same unconditional pattern on `accountid` could zero a line's account if `inventory.incaccount` is unset — revisit only if GL mis-posting appears. Relates to [[evolution_float_money_columns]].
