Dentolize · Accounting Module Walkthrough
On this pageBusiness viewTechnical view

Tax, Inventory Valuation & Healthcare Reports

This page groups three smaller, less central pieces of the module that are nonetheless worth understanding on their own terms: tax tracking, inventory cost valuation, and a set of dental-clinic-specific analytical reports.

Business view

Tax has three separate screens that are easy to confuse:

  • Tax Codes is reference data — a list of tax rates and types (VAT, withholding tax, zero-rated, exempt, out-of-scope, Zakat) a clinic can attach to things for labeling purposes.
  • Tax Returns is a filing workflow — generate a return for a period, file it, then record payment against it. Today this only works for VAT.
  • Tax (the overview screen) is a live dashboard — VAT, withholding tax, and Zakat figures computed on the spot from the ledger, plus a ZATCA e-invoicing cross-check, none of it saved anywhere.

Inventory Valuation answers one question: does the value the general ledger says is sitting in the Inventory account match what the inventory subledger (the actual stock records) says it should be? It also drives which costing method (standard cost, weighted average, FIFO, or LIFO) is used to value stock consumed by procedures.

Healthcare reports are three dental-clinic-specific views: how much is owed to each doctor, how profitable each doctor is (production minus their commission), and how old the clinic's open insurance claims are.

Technical view

Tax Codes

TaxCode (TaxType enum packages/prisma/schema.prisma:8205-8212, TaxCode model :8214-8232) — type (TaxType: VAT | WHT | ZERO_RATED | EXEMPT | OUT_OF_SCOPE | ZAKAT), rate, country, and optional links to a payable/receivable/expense account. addTaxCode/editTaxCode/setTaxCodeActive (packages/server/src/resolvers/mutations/ledgerMutations.js:977/997/1016-1022), gated MANAGE_COA.

A tax code doesn't actually drive any posting. JournalLine.taxCodeId exists as an optional dimension, but no resolver anywhere (invoice posting, expense posting, manual journal entry) ever sets it, and no screen lets a user pick a tax code when building a journal line. Real VAT postings go through the account-role system instead (Setup & Foundations — roles VAT_PAYABLE/VAT_RECEIVABLE), which is entirely independent of TaxCode. TaxCode's account links are populated once at company setup to mirror those role defaults, but nothing reads them back later. In its current state, treat Tax Codes as informational reference data — editing one has no effect on where a real transaction posts.

Tax Returns

TaxReturn (schema.prisma:8758-8780) — the model's own comment describes a DRAFT → GENERATED → FILED → PAID lifecycle for VAT/WHT/Zakat, but generateTaxReturn (ledgerMutations.js:1579) hard-codes a rejection of any type other than 'VAT' — WHT and Zakat returns can't actually be generated despite the schema and UI type field suggesting otherwise. fileTaxReturn (:1614, GENERATED → FILED) and recordTaxPayment (:1624, FILED → PAID, posts against a treasury) round out the flow. generateTaxReturn/fileTaxReturn need MANAGE_COA; recordTaxPayment needs POST_JOURNAL.

Not connected to ZATCA. Despite the name overlap, this workflow has zero references anywhere in packages/zatca (the actual Saudi e-invoicing package — XML signing, QR codes, submission to ZATCA's API). "Filing" a return here only means marking the internal document FILED; it does not submit anything to ZATCA.

Tax overview

tax/TaxScreen.js — four tabs, all read-only and computed live (nothing persisted): VAT (output minus input VAT for the period, straight off GL account balances), WHT (withholding tax withheld per supplier), Zakat (a computed base and 2.5% due, explicitly labeled an estimate — the code's own disclaimer: "Planning figure — confirm against ZATCA Zakat rules before filing"), and ZATCA (zatcaReconciliation — a read-only cross-check comparing GL output-VAT against invoices' e-invoicing submission status, flagging anything posted to the ledger with VAT that hasn't been reported to ZATCA yet; this is a diagnostic, not a filing integration). All four require only VIEW_REPORTS.

Inventory Valuation

Company.inventoryCostingMethod (field at schema.prisma:269, InventoryCostingMethod enum: STANDARD | WEIGHTED_AVERAGE | FIFO | LIFO, :7051-7057) is a real, wired-up setting — resolveConsumptionCost (packages/server/src/resolvers/mutations/actions/inventory/inventoryCosting.js:34-80) implements all four: weighted average as a running value/quantity, FIFO/LIFO by walking cost layers ordered by receipt date, standard as the current price. This is independent of the physical stock-picking order, which always stays FEFO (nearest-expiry-first) regardless of the costing method chosen — the setting only decides which cost is charged to COGS, not which physical unit leaves the shelf. Configured via updateAccountingControls (web-only, on the Automations screen).

inventoryValuation/InventoryValuationScreen.js (inventoryValuationReport, packages/server/src/accounting/reports/inventory.js) is a straightforward tie-out: GL account 1300 (Inventory) balance versus Σ InventoryItem.value, flagged reconciled if the difference is under 0.01. It does not itself compare costing methods — it just checks whatever value the costing method already produced, so it's a GL-vs-subledger check, not a "what would FIFO show instead" tool. Permission: VIEW_REPORTS.

Healthcare reports

healthcare/HealthcareScreen.js — three tabs, all backed by packages/server/src/accounting/reports/healthcare.js: Doctor Payables (per-doctor balance owed, from GL account 2400), Doctor Profitability (production minus accrued commission, from GL account 5210), Claims Aging (open insurance claims bucketed by age since submission, per insurer). A fourth report, insuranceArReconciliation (reconciling insurer AR against open claims, surfacing unclaimed AR and settlement-clearing balances), and the Bulk Insurance Settlement flow described in Cash, Banking & Payables, used to be web-only extras on the Healthcare screen — both now have mobile screens too (insuranceReconciliation/InsuranceReconciliationScreen.js and bulkSettlement/BulkSettlementScreen.js), so this is no longer a mobile/web parity gap. Permission: VIEW_REPORTS for all healthcare queries.

Worth noting separately: eInvoiceSettings/EInvoiceSettingsScreen.js (its autoReReportOnEdit/autoSubmit toggles) is a real integration point with the pre-existing ZATCA e-invoicing system — unlike TaxCode/TaxReturn above, which are confirmed disconnected from ZATCA, these settings genuinely gate ZATCA submission/re-reporting behavior in packages/server/src/services/eInvoices/. Don't conflate the two when explaining "what talks to ZATCA" in this module.