MemberJunction / MemberJunction/MJ

No way to mark a NOT NULL column as server-assigned, so minted identifiers (order/invoice/contract numbers) cannot be created through the UI without a workaround

Open
#4,001 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TSQL
Stars
29
Forks
6
Avg merge
2d 1h
Merged PRs (30d)
323

Description

## Summary

MJ has no way to say **"this column is NOT NULL, but the server assigns it on insert."** Nullability is the only signal, so a server-assigned column is indistinguishable from one the user must fill: the client-side validator refuses the create before the server can mint a value, and the form renders the field as required-and-empty.

The result is that a perfectly ordinary pattern — a sequence-backed document number minted in `Save()` — cannot be created through the UI at all without a workaround.

## Where it comes from

`EntityField.Validate()` (`packages/MJCore/src/generic/baseEntity.ts`, ~line 315):

```ts
if (!ef.ReadOnly && !ef.SkipValidation) {
if (!ef.AllowsNull && (this.Value === null || this.Value === undefined)) {
if (ef.DefaultValue is empty) -> ERROR
else if (this._OldValue != null) -> ERROR // existing record only
}
}
```

and the form's required styling (`form-field.component.ts`):

```ts
get IsRequired(): boolean { return this.FieldInfo?.AllowsNull === false; }
```

So there are exactly two escapes, and each is a side effect rather than an expression of intent:

1. **A `DefaultValue`.** MJ already treats a defaulted NOT NULL column as legal-when-null on a *new* record — which is precisely the semantic wanted. But a real DB default whose definition is an *expression* breaks CodeGen (it string-quotes it into `spCreate`, which then fails to compile and is left dropped — see the companion issue), so this escape is unavailable for anything but a literal.
2. **`AllowUpdateAPI = false`**, which makes the field `ReadOnly`, which makes `SkipValidation` true, which skips the check. This works, and it is what we shipped — but it is a blunt instrument: it also permanently forecloses *deliberate* manual entry, and it says "not writable through the API" when the truth is "written by the server, not by the user."

Neither says what is actually true, and both are discovered by reading MJ's internals rather than by reading the field's metadata.

## What we'd like

An explicit flag on `EntityField` — `IsServerAssigned` (or `AssignedOnInsert`) — meaning: *the value is supplied during insert by server-side logic; do not demand it from the user and do not treat its absence as invalid on a new record.*

Behaviour it should drive:

- `EntityField.Validate()` skips the not-null check **on a new record only** (an existing record with a null value is still wrong).
- `form-field`'s `IsRequired` returns false, so no red required styling and no "cannot be null" on save.
- The field stays fully writable, so an explicit value supplied by the caller is still honoured — which is the part `AllowUpdateAPI = false` gives up.
- Ideally the generated form renders it read-only-with-explanation on create rather than as an empty required input.

## Why it is worth a first-class flag

This is not a niche case — it is every minted identifier: order numbers, invoice numbers, contract numbers, ticket numbers, any `NEXT VALUE FOR`-backed sequence. Each one currently has to rediscover the same two workarounds and pick the least-bad. A flag makes the intent legible in metadata, keeps validation honest for existing records, and removes the incentive to reach for a DB default (which is currently actively dangerous per the CodeGen issue).

## Context

Found in `bizapps-contracts`, MJ `6.1.0-edge.2/3`: `Contract.ContractNumber` is NOT NULL and minted by `ContractEntityServer.Save()` from `seq_ContractNumber` via `spAssignNextContractNumber`. Until `AllowUpdateAPI = false` was applied, every browser create failed with "Contract Number cannot be null" and users hand-typed numbers instead.

Contributor guide

Open the contributing guide

Research direction

Start with EntityField.Validate() in packages/MJCore/src/generic/baseEntity.ts and IsRequired in form-field.component.ts. Trace how EntityField metadata reaches validation and generated forms, then define the new flag's behavior for new versus existing records. Done means server-assigned NOT NULL fields can be created without a value, remain writable for explicit values, and are not shown as required.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
full-stack
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.