Skip to main content

Validation Rules

This page documents the cross-cutting validation rules the system enforces across modules: ownership-holding rules, field formats, the password policy, optimistic locking, soft deletes, auto-numbering, multi-tenant isolation, and when validation runs.

For entity-specific rules (status transitions, delete blocks), see Status Lifecycles and Delete Guardrails.


Ownership holdings (100% + managing partner)

Holdings represent ownership percentages and are validated whenever holdings are saved or a partnership is activated.

RuleDetail
Sum to 100%Active holding percentages must total exactly 100.00%. The check uses a tolerance of Math.abs(sum - 100) > 0.0001, so any deviation beyond rounding is rejected.
Exactly one managing partnerAmong active holdings there must be exactly one managing partner. Zero managing partners and more than one are both rejected.
Inactive rows excludedInactive holding rows are excluded from the sum and the managing-partner count. An inactive partner cannot be designated as managing partner.
No implicit rebalancingThe system never auto-adjusts holdings. If a change breaks the 100% sum, the save is rejected and the user must fix it explicitly.

Error messages (as returned by the backend):

  • Active holding percentages must total 100.00%. Current total: <n>%.
  • Exactly one active managing partner is required.
  • Only one managing partner is allowed. Currently selected: <n>.
  • Inactive partner cannot be assigned as a Managing Partner.

The same 100% / single-managing-partner principle applies to partnership holdings and to unit shareholder holdings.


Field formats

Format validation is applied to the relevant master and transactional fields.

FieldFormatExample
PAN10 characters: five letters, four digits, one letterABCDE1234F
Aadhaar12 digits; displayed masked (last 4 shown)XXXX XXXX 1234
GSTIN15 characters (state code + PAN + entity + check digit)22ABCDE1234F1Z5
Percentage0.00100.00, two decimal places33.33
CurrencyPositive, two decimals, Indian grouping1,00,000.00
Phone / MobileInternational format with country code+91 9876543210
EmailValid email format; unique within the organization where requireduser@example.com
Names / textTrimmed; typically 2–255 characters
DatesDD/MM/YYYY; logical ordering enforced (e.g. end after start)08/06/2026

PII display rules: Aadhaar and PAN are shown with only the last 4 characters; bank account numbers are masked in the middle. Passwords are never displayed or logged.

Future-date guards: Several modules reject future dates where they make no business sense — for example, a project start date and a stock allocation date cannot be in the future.


Password policy

RequirementDetail
Minimum length8 characters
CompositionAt least one uppercase, one lowercase, one number, and one symbol
StorageHashed with bcrypt; never stored or logged in plain text
ResetVia a single-use token (see Auth Flows)

Optimistic locking (409 Conflict)

To prevent two users from silently overwriting each other's changes, edits use optimistic locking based on the record's updatedAt timestamp.

  • When you open a record, you receive its current updatedAt.
  • On save, the client sends back that updatedAt. The backend compares it to the current value in the database.
  • If they differ, the record changed since you loaded it. The save is rejected with HTTP 409 Conflict.

Standard message: "This record changed since you opened it. Refresh and try again."

This is enforced on edits such as Sales Invoices and partnership holdings. Resolve a 409 by refreshing the record, re-applying your change, and saving again.


Soft deletes

Deletions are soft across the system.

  • Records carry a deletedAt timestamp. "Deleting" sets deletedAt rather than removing the row.
  • All standard reads filter on deletedAt: null, so soft-deleted records disappear from lists but remain for audit and referential integrity.
  • Because deletes are soft, history and audit trails are preserved.
  • Deletion is still blocked when a record is referenced by live downstream data — see Delete Guardrails.

Auto-numbering of codes

Human-readable identifiers are generated automatically using configurable prefixes (Settings → Naming Conventions) and zero-padded sequences.

EntityPatternExample
ProjectPRJ-###PRJ-001
SubprojectSP-###SP-001
QuotationQT-###QT-001
Sales OrderSO-###SO-001
Sales InvoiceSI-###SI-001
Purchase OrderPO-###PO-001
Stock(configured prefix)STK-057
Stock allocation<ProjectCode>-ALLOC-###PRJ-001-ALLOC-001
Split child stock<ParentCode>-<A,B,C…>STK-057-A, STK-057-B

Numbering is sequential per organization. On rare collision (concurrent creation), the backend retries to obtain the next free number. Codes are unique within the organization.


Multi-tenant isolation

Every business record is scoped to an organization via organizationId.

  • All queries are filtered by the caller's organizationId; a user can never read or write another organization's data.
  • Uniqueness constraints (stock code, project code, party email/mobile, unit number within a subproject, etc.) are evaluated within the organization, not globally.
  • Subproject-scoped access narrows this further for users with subproject roles — see RBAC Matrix.

Sensitive data protection

Personal and financial data (PAN, Aadhaar, GSTIN, contact details, bank account numbers, sensitive custom fields, and sensitive files) is protected in two ways:

  • Masked by default. Responses return masked values (for example **** + last 4 characters); the full value is shown only through a permission-gated, audited reveal.
  • Validated before protection. Field-format rules (PAN, Aadhaar, GSTIN, phone — see Field formats) still run on the entered value first, so masking never hides an invalid entry.

For the full masking formats, the reveal flow, file sensitivity tiers, and which roles can reveal, see Data Security.


When validation runs

AspectBehaviour
TriggerValidation runs on submit — not on blur and not while typing.
FocusOn failure, focus jumps to the first invalid field.
Error displayThe most critical error per field is shown beneath the field in red, with a red field border. The error clears once the field becomes valid.
Server authorityThe frontend validates for convenience; the backend is authoritative. A request that passes client checks can still be rejected server-side (e.g. uniqueness, holdings sum, invalid transition).

Uniqueness constraints (summary)

EntityMust be unique (within organization)
PartyEmail, Mobile
StockStock code
ProjectProject code, Project name
SubprojectSubproject code
UnitUnit number (within its subproject)
PartnerPartner name; mobile/email combination
PartnershipPartnership code, Partnership name
Bank accountAccount number (within organization)

Violations return HTTP 409 Conflict with a field-level message indicating which value is already in use.