This commit is contained in:
Dhananjaya99
2026-07-18 23:42:58 +05:30
parent 80b130dffb
commit 92c4b14a6c
55 changed files with 8815 additions and 43 deletions
+9 -6
View File
@@ -194,7 +194,7 @@ One base currency; invoicing/3-way match in Accounting (GRN carries data); users
| FR-X-05 | No editing/deleting confirmed/posted transactions; corrections via reversing entries. | M |
## B.4 Data Model (summary)
Key entities and relationships are enumerated in **Part C**. The commitment: Item, UOM/UOMConversion, Category, SubCategory, Brand, ItemType, ProductConfig, Vendor, Warehouse/Bin, ItemReorder, Requisition(+Line), RFQ(+Line)/VendorQuotation, PurchaseOrder(+Line), GRN(+Line), PurchaseReturn(+Line), StockLayer (FIFO), StockLedger (immutable), Batch, Serial, StockTransfer(+Line), StockAdjustment(+Line), StockCount(+Line), User, ReasonCode, NumberSequence, AuditLog, JournalEntryStub, and reserved RBAC (Role, Permission, UserRole, RolePermission).
Key entities and relationships are enumerated in **Part C**. The commitment: Item, UOM/UOMConversion, Category, SubCategory, Brand, ItemType, ProductConfig, Vendor, Warehouse/Bin, ItemReorder, Requisition(+Line), RFQ(+Line)/VendorQuotation, PurchaseOrder(+Line), GRN(+Line), PurchaseReturn(+Line), StockLayer (FIFO), StockLedger (immutable), Batch, Serial, StockTransfer(+Line), StockAdjustment(+Line), StockCount(+Line), User, ReasonCode, NumberSequence, AuditLog, JournalEntryStub, and RBAC for sidebar visibility (Role, NavItem, SubNavItem, Permission, RolePermission — see C.8; per-endpoint enforcement still deferred).
## B.5 External Interfaces
UI: responsive; count/pick screens handheld-friendly; status badges; mandatory-field validation. Hardware: barcode/QR (designed-for). Software: relational DB with transactional integrity + row locking; internal service interfaces/events for Phase-2+ modules.
@@ -348,13 +348,16 @@ AUDIT_LOG(audit_id PK, user_id FK→USER, entity_type, entity_id, action, change
JOURNAL_ENTRY_STUB(journal_id PK, source_doc_type, source_doc_id, debit_account, credit_account, amount)
```
## C.8 Reserved (RBAC — deferred, schema placeholder only)
## C.8 RBAC — sidebar-visibility only (implemented 2026-07-18); per-endpoint enforcement still deferred
```
ROLE(role_id PK, name)
PERMISSION(permission_id PK, code)
USER_ROLE(user_id FK→USER, role_id FK→ROLE)
ROLE(role_id PK, auth_role_id [GUID, unique] → AuthHex Role, code, name, is_system_role, status, created_at, updated_at, row_version) -- local shadow/projection of AuthHex's Role, same pattern as USER
NAV_ITEM(nav_item_id PK, code, label, icon, href, sort_order, status) -- top-level sidebar entry; seeded to match the frontend
SUB_NAV_ITEM(sub_nav_item_id PK, nav_item_id FK→NAV_ITEM, code, label, icon, href, sort_order, status)
PERMISSION(permission_id PK, code, nav_item_id FK→NAV_ITEM [nullable], sub_nav_item_id FK→SUB_NAV_ITEM [nullable]) -- exactly one of the two FKs is set
ROLE_PERMISSION(role_id FK→ROLE, permission_id FK→PERMISSION)
USER(..., role_id FK→ROLE [nullable]) -- added to the existing USER shadow (see C.7)
```
Note: `USER_ROLE` from the original placeholder sketch was dropped — a user has at most one role (`USER.role_id`), matching AuthHex's own `User.RoleId` being a single scalar FK, not a many-to-many.
## C.9 Modeling notes (load-bearing)
- **Item types are a dropdown, not a relationship.** `ITEM_TYPE` (Color, Size, Material) exists **only** to populate the frontend item-builder's dropdown via `GET /item-types`. Nothing references it and it references nothing — there is no value table and no join to `ITEM`. The builder cross-products the checked types into **one standalone item per combination**; the chosen values (Red, S, M) are encoded by the **client** into the generated SKU (`BL-0002` for one type, `BL-100-0003` for two) and the server only checks that SKU for uniqueness. **The item list is the record of what was built.** This is not a product-variation model: there is no parent-product entity and no variant hierarchy.
@@ -366,7 +369,7 @@ ROLE_PERMISSION(role_id FK→ROLE, permission_id FK→PERMISSION)
- **In-transit + cost-preserving transfer.** `STOCK_TRANSFER` holds `src`/`dest` warehouse; dispatch consumes source layers into in-transit, receive creates the destination layer at the **inherited** source cost.
- **FEFO ≠ FIFO.** FIFO governs *costing*; FEFO governs *physical picking* of perishables via `BATCH.expiry_date`.
- **External IdP + shadow user.** Authentication is delegated to **AuthHex** (RS256, issuer `AuthHex`/audience `AuthHexClient`, static public key). `USER` is a **local shadow** of AuthHex identities: `auth_user_id` (GUID from the token's `UserId` claim) is JIT-mapped to the local `int` `user_id` that every `created_by`/`requested_by`/`AUDIT_LOG.user_id`/`STOCK_LEDGER.user_id` FK references — no FK type change. A door policy admits only ERP `UserType`/`Role` holders.
- **Reserved RBAC.** Role/Permission/UserRole/RolePermission exist for schema-completeness only; only `USER` is live (audit stamp). AuthHex's `RoleCode`/`UserTypeCode` claims drive the door gate today; per-endpoint RBAC is future work.
- **RBAC — sidebar visibility, not endpoint enforcement (2026-07-18).** `Role`/`NavItem`/`SubNavItem`/`Permission`/`RolePermission` are now live tables backing Role CRUD (`RolesController`) and a permission-assignment UI. AuthHex remains the source of truth for `Role` identity (Guid PK, referenced by its JWT `RoleId`/`RoleCode` claims); ERPCore's `Role` is a **local shadow synced on write**`RolesController` calls AuthHex's new `/api/role` functions first, then mirrors the result into the local int-keyed row (`auth_role_id` maps the two), exactly like `USER`/`auth_user_id`. `GET /api/v1/auth/me` resolves the caller's `RoleCode` claim to its local `Role`, joins `RolePermission`, and returns the permitted `NavItem`/`SubNavItem` codes for the frontend to filter its sidebar by. **This is deliberately UI-only**: no endpoint in this API (including the new Role/User/Nav ones) gained an authorization check from this work — AR-01 in `02-SECURITY.md` is unchanged, and per-endpoint RBAC remains future work (Part D there).
- **Reorder alerts are a query**, not an entity — computed from `ITEM_REORDER` vs available. Add a table only if alert history is required.
## C.10 Entity → implementation mapping
+51
View File
@@ -111,6 +111,57 @@ Request/response field shapes match AuthHex's own payloads one-for-one (project-
session-issuing responses omit `AccessToken`/`RefreshToken` (cookie-delivered instead) and `refreshToken` is read from the
`erp_rt` cookie rather than the request body.
### 2.0.1 RBAC — Roles, sidebar nav, Users (added 2026-07-18; sidebar-visibility only, see docs/10 C.8)
`GET /api/v1/auth/me` — the frontend's authoritative source for the current session's role and permitted sidebar sections
(replaces the previous client-only `roleId` cached in localStorage). No payload.
**200 OK**
```json
{ "roleCode": "ADMIN", "roleName": "Administrator", "navCodes": ["dashboard", "products", "products.item", "settings.roles", "..."] }
```
`GET /api/v1/nav` — read-only sidebar tree (`NavItem` + nested `SubNavItem`), seeded to mirror the frontend's hardcoded
sidebar (`components/Layouts/AppSidebar.tsx`); used to render the Role permission-assignment checkbox UI. Not admin-editable
in this phase.
**Roles** (`RolesController`) — `Role` is a **local shadow of AuthHex's Role** (same pattern as `USER`/`auth_user_id`,
docs/10 C.9): every write below forwards to AuthHex's new `/api/role` functions first, then mirrors the result locally.
| Route | Notes |
|---|---|
| `GET /roles` | Paged list; `q`, `status` filters. |
| `GET /roles/{roleId}` | `ETag` header for `If-Match` on update. |
| `POST /roles` | `{ code, name }``201`, forwards to AuthHex `createRole`. |
| `PUT /roles/{roleId}` | Requires `If-Match`; forwards to AuthHex `updateRole`. |
| `PATCH /roles/{roleId}/status` | `{ status }``204`. |
| `DELETE /roles/{roleId}` | Forwards to AuthHex `deleteRole`; `409 ROLE_IN_USE` if any user still holds it. |
| `GET /roles/{roleId}/permissions` | `{ roleId, navItemIds, subNavItemIds }`. |
| `PUT /roles/{roleId}/permissions` | Replaces the role's full permission set from `{ navItemIds, subNavItemIds }` — purely local, no AuthHex call. |
> **`code` is server-accepted but frontend-derived, never hand-typed (2026-07-18).** The Roles UI computes `code` from
> `name` (uppercased, non-alphanumeric → `_`) and submits it read-only; the field stays free-form here for API callers,
> but no UI lets an operator type or edit it directly, on create or later. The Create Role dialog also now includes the
> permission checkbox tree, so `POST /roles` and `PUT /roles/{roleId}/permissions` fire as one user action.
>
> **Bug fixed (2026-07-18): `POST /roles`/`PUT /roles/{roleId}` 500ing via `AUTH_UPSTREAM_ERROR`.** `IsSystemRole` being
> omitted serialized as JSON `null`, and AuthHex's `createRole`/`updateRole` called `JsonElement.GetBoolean()` on it
> unconditionally when the key was present — which throws on `null` (unlike `GetString()`, which tolerates it). Fixed on
> both sides: AuthHex now checks `ValueKind != JsonValueKind.Null` before reading `isSystemRole`, and ERPCore's
> `AuthHexClient` now serializes with `JsonIgnoreCondition.WhenWritingNull` so unset nullable fields are omitted from the
> payload entirely rather than sent as explicit nulls — closing this class of bug for any other nullable field sent to AuthHex.
**Users** (`UsersController`) — manages the local shadow `User` table and orchestrates account creation in AuthHex.
| Route | Notes |
|---|---|
| `GET /users` | Paged list, joined with `Role`. |
| `GET /users/{userId}` | Single record. |
| `POST /users` | Creates the account in **both** backends: calls AuthHex's `registerUser` (which persists the password and emails it to the given `email`), then immediately mirrors the local shadow `User` row (rather than waiting for next-login JIT provisioning). Body: `{ username, fullName, roleId, userTypeId, email, nic?, mobileNumber?, password? }` (`password` empty ⇒ AuthHex auto-generates one). |
| `PUT /users/{userId}/role` | `{ roleId }` — local role reassignment only; status/lock changes reuse the existing `/auth/status` and `/auth/lock` proxy endpoints. |
| `GET /users/user-types` | Added 2026-07-18. Proxies AuthHex's new `listUserTypes``[{ userTypeId, code, description }]`. Populates the Create User form's UserType select so operators pick from a real list instead of typing an AuthHex GUID by hand; defaults to the sole existing type when only one exists. |
> **Known gap, not fixed (flagged 2026-07-18):** AuthHex's `loginUser` resolves `identifier` against `Email`/`MobileNumber`/`Nic`
> only — **not** `Username` (`UserManageRepository.GetUserByIdentifierAndType`). A user created via `POST /users` can log in
> with their email but not their username. Out of scope for this change; revisit if/when asked.
### 2.1 Items
> **`itemType` → `stockNature` (2026-07-16).** The Stocked/NonStocked/Service field was renamed so the name `itemType` could be taken by the new Item Type master (§2.7) — an unrelated concept. Items gained `subCategoryId` and `brandId` (both nullable). Items carry **no** item-type reference: the values chosen in the builder are encoded into the client-generated SKU (docs/10 Part C.9).
+3 -1
View File
@@ -28,7 +28,9 @@ Principles:
## 2. User flows
The flow below is the end-to-end Phase-1 journey: replenishment need → procurement → receiving → QC hold → stock available → stock operations, with the reorder loop closing back. Colour = functional role (roles are conceptual; RBAC is not enforced in Phase 1).
The flow below is the end-to-end Phase-1 journey: replenishment need → procurement → receiving → QC hold → stock available → stock operations, with the reorder loop closing back. Colour = functional role. Roles now drive real sidebar visibility (`GET /auth/me`'s `navCodes`, see docs/10 C.8/docs/11 §2.0.1, admin screens at `/dashboard/settings/roles` and `/dashboard/settings/users`) but **per-endpoint RBAC is still not enforced** — this remains a UI-level filter only.
> **Roles screen (2026-07-18):** `code` is never typed by an operator — it's derived client-side from `name` (uppercased, non-alphanumeric → `_`) and shown read-only, on both create and edit. The Create Role dialog also includes the permission checkbox tree (`components/auth/RolePermissionTree.tsx`, fed by `GET /nav`), so creating a role and assigning its sidebar permissions is one Save action; the detail page (`/dashboard/settings/roles/[id]`) remains for later edits. The Create User dialog's "User type" is a `<Select>` populated from `GET /users/user-types` (proxying AuthHex's `listUserTypes`), defaulting to the sole existing type — operators never type an AuthHex UserType GUID by hand.
```mermaid
flowchart TD