v0.2.0 — PLC & unit-charge pricing; GST and collection correctness fixes
Upgrade note: upgrade-native.sh now syncs new PERMISSIONS rows to
existing installs automatically (see the permission-delivery fix below),
but it does not — and should not — grant them to any role for you. After
upgrading, an admin must open Admin → Roles, edit each role that
should get the new PLC/charge-management capability, check
inventory.unit.plc-manage / inventory.unit.charge-manage, and save.
This is a deliberate, permissions-only sync — see the entry below for why
auto-granting them would be its own bug, not a fix.
Fixed
-
Correctness fix affecting GST charged on bookings and extra charges —
check your invoices if your company's GST State Code was ever unset.
isIntraStateSupply()used to silently default to intra-state
(CGST+SGST) whenever the company's GST state code or a booking's
place-of-supply state code was missing, instead of raising an error.
Any company whose Company Config GST fields were never filled in —
every install created before those columns existed, or any install
where nobody had visited Company Config yet — has been charging
CGST+SGST on every booking and extra charge regardless of where the
property actually is, which is the WRONG tax treatment (IGST) whenever
the real place of supply is in a different state from the company's
own. This produced no error and no warning; the only way to notice was
to already know the correct treatment and check by hand. If this
applies to you, review bookings/extra charges made while your GST
config was incomplete and correct any wrongly-taxed invoices — this
release does not retroactively fix already-issued invoices. Going
forward,isIntraStateSupply()now throws instead of guessing — a
booking or extra charge with incomplete GST config is rejected with a
clear error naming what to set in Company Config, rather than silently
taxed wrong. The app also now logs a warning at boot listing any
company with incomplete GST config, and the staff admin UI shows a
persistent banner linking to Company Config until it's completed. -
Correctness fix affecting reported collection figures — upgrade and
let the migration run before trusting any collection report. A prior
bug (see the REPORTS-phase entry inCLAUDE.md) left bounced-cheque
receipts still marked as collected (is_reversed = false) in every
collection report, rollup, and the customer portal's own payment
history — the code fix stops this going forward, but does nothing for
receipts that already bounced before you upgrade. Migration
20260804120000_backfill_bounced_receipt_is_reversedcorrects those
existing rows on your nextprisma migrate deploy(part of the normal
upgrade path — no manual step needed). It only touches the
is_reversed/reversal_reasonflag on affected receipts; it does not
alter any ledger entry, allocation, or installment. If your reported
collection totals looked too high before upgrading, they'll drop by
the sum of any previously-bounced cheques once this runs. -
A release that adds a
PERMISSIONSconstant never reached an
existing install — only a fresh one.seed.ts's permission-upsert
loop ran unconditionally, but the early-return gate right after it
(if (existingCompany) return) meant no other seeded content, and
critically no later permission addition, was ever re-applied to a
company created in an earlier release.upgrade-native.shnow runs a
dedicatedsync-permissions.tsstep (idempotent, permissions-table
only) on every upgrade, so new permission rows reach existing installs
the same way they reach fresh ones. Deliberately does NOT extend to
masters or roles — see the upgrade note above andCLAUDE.mdfor why
that's a different (and mostly correct-as-is) problem. -
A system role (
super_admin,company_admin, etc.) could never be
granted a newly-added permission through the UI, at all.
RolesService.update()rejected any change to a system role, not just
a rename — so even with the fix above delivering the permission row,
there was no way to actually grant it to a role. Scoped the guard to
an actual name change only; a system role's permission set is now
freely editable (its name and existence stay protected, as before).
Added
- PLC and unit-charge management. Unit-level PLCs (park-facing,
corner, etc.) and extra charges (IFMS, legal, etc.) can now be
assigned per unit from the Inventory → Project → Pricing panel, and
flow into a booking's cost breakup and the confirm step's total
automatically. PLC amounts are snapshotted in paise at assignment time
(from a percentage of the unit's rate, or a flat amount) and never
retroactively change if the base rate is revised later. - Per-charge-type GST rates. Charge Types now carry their own
optional GST rate and HSN/SAC code (Masters → Charge Types). A cost
line's GST resolves in order: its own rate, then its charge type's
rate, then the booking's base-line rate — never silently zero-rated.
A PLC line has no charge type, so it always inherits the base line's
rate; this is stated explicitly rather than left implicit.
Changed
- Replaced the
argon2password-hashing dependency with
@node-rs/argon2(pure Rust via napi-rs).argon2had caused two
separate deployment failures on two different platforms (an
Alpine/musl prebuild issue, and a still-unexplained SIGSEGV
crash-loop on GitHub'subuntu-latestCI runners — seeCLAUDE.md
for the full, ultimately-inconclusive investigation); the replacement
ships prebuilt binaries for every platform this project targets and
never falls back to compiling from source. Existing stored password
hashes remain verifiable — both libraries use the same standard PHC
string format, confirmed by directly cross-verifying a real hash
between the two before switching. No action needed on upgrade, no
password resets.