refactor: improve code formatting and organization across multiple components

- Enhanced readability by restructuring code formatting in various components, including API documentation, user management, and sales modules.
- Standardized table and object definitions for better clarity in API documentation.
- Improved layout and indentation in React components for better maintainability.
- Updated unit tests to reflect changes in formatting and ensure consistency.

These changes enhance the overall code quality and maintainability of the project, making it easier for developers to navigate and understand the codebase.
This commit is contained in:
shancheas
2026-08-27 13:09:57 +07:00
parent 105cf3030a
commit 6b012a6aae
44 changed files with 371 additions and 260 deletions
+106 -119
View File
@@ -25,10 +25,10 @@ Base URL: `http://localhost:{PORT}` (default **3000**). There is **no** global p
### Tokens
| Token | Type | Default lifetime | Transport |
| ----- | ---- | ---------------- | --------- |
| Access | JWT HS256 | `15m` (`JWT_ACCESS_EXPIRES_IN`) | `Authorization: Bearer …` |
| Refresh | Opaque 64-char string | 7 days (`REFRESH_TOKEN_EXPIRES_IN_MS`) | JSON body `refreshToken` |
| Token | Type | Default lifetime | Transport |
| ------- | --------------------- | -------------------------------------- | ------------------------- |
| Access | JWT HS256 | `15m` (`JWT_ACCESS_EXPIRES_IN`) | `Authorization: Bearer …` |
| Refresh | Opaque 64-char string | 7 days (`REFRESH_TOKEN_EXPIRES_IN_MS`) | JSON body `refreshToken` |
Refresh **rotates**: each successful `POST /auth/refresh` returns a new pair; the old refresh token is invalid.
@@ -111,11 +111,11 @@ CSV import uses `multipart/form-data` with field name **`file`** (max 1 MiB).
Query (all optional):
| Param | Rules | Default |
| ----- | ----- | ------- |
| `page` | integer ≥ 1 | `1` |
| `limit` | 1**200** | `10` |
| `offset` | integer ≥ 0 | — |
| Param | Rules | Default |
| -------- | ----------- | ------- |
| `page` | integer ≥ 1 | `1` |
| `limit` | 1**200** | `10` |
| `offset` | integer ≥ 0 | — |
If both `page` and `offset` are sent, **`page` wins**. `offset` maps to `page = floor(offset / limit) + 1`.
@@ -123,7 +123,9 @@ Public response:
```json
{
"data": [ /* items */ ],
"data": [
/* items */
],
"meta": {
"currentPage": 1,
"itemCount": 10,
@@ -140,15 +142,15 @@ Public response:
Unwrapped resource object, or:
| Operation | Status | Body |
| --------- | ------ | ---- |
| Create | `201` | resource DTO (detail shape) |
| Update / status | `200` | resource DTO |
| Delete | `204` | empty |
| Bulk delete | `200` | `{ "deleted": number }` |
| Bulk status | `200` | `{ "updated": number }` |
| Import | `200` | `{ "imported": number }` |
| Generate plans | `200` | `{ "created": number, "skipped": number }` |
| Operation | Status | Body |
| --------------- | ------ | ------------------------------------------ |
| Create | `201` | resource DTO (detail shape) |
| Update / status | `200` | resource DTO |
| Delete | `204` | empty |
| Bulk delete | `200` | `{ "deleted": number }` |
| Bulk status | `200` | `{ "updated": number }` |
| Import | `200` | `{ "imported": number }` |
| Generate plans | `200` | `{ "created": number, "skipped": number }` |
### Errors (NestJS default)
@@ -162,14 +164,14 @@ Unwrapped resource object, or:
`message` is a string or an array of validation strings.
| Status | Typical cause |
| ------ | ------------- |
| `400` | Validation, extra fields, invalid VO (phone/date/status), `status` on PATCH |
| `401` | Missing/expired/revoked JWT, bad credentials, invalid refresh |
| `403` | `Insufficient privilege` |
| `404` | `{Resource} not found` (for example `Branch not found`) |
| `409` | Unique conflict (username, code, cycle/plan already exists) |
| `429` | Rate limit |
| Status | Typical cause |
| ------ | --------------------------------------------------------------------------- |
| `400` | Validation, extra fields, invalid VO (phone/date/status), `status` on PATCH |
| `401` | Missing/expired/revoked JWT, bad credentials, invalid refresh |
| `403` | `Insufficient privilege` |
| `404` | `{Resource} not found` (for example `Branch not found`) |
| `409` | Unique conflict (username, code, cycle/plan already exists) |
| `429` | Rate limit |
CSV batch failure:
@@ -189,12 +191,12 @@ Core (configuration, privileges, cycles, plans, settings): `draft` | `active` |
Sales statuses (use **only** these on that resource):
| Resource | Allowed |
| -------- | ------- |
| Sales request | `draft`, `pending`, `approved`, `rejected` |
| Sales order / packing slip | `draft`, `processed`, `completed`, `cancelled` |
| Sales invoice | `draft`, `processed`, `partial`, `completed`, `cancelled` |
| Sales payment | `draft`, `pending`, `approved`, `rejected` |
| Resource | Allowed |
| -------------------------- | --------------------------------------------------------- |
| Sales request | `draft`, `pending`, `approved`, `rejected` |
| Sales order / packing slip | `draft`, `processed`, `completed`, `cancelled` |
| Sales invoice | `draft`, `processed`, `partial`, `completed`, `cancelled` |
| Sales payment | `draft`, `pending`, `approved`, `rejected` |
### Dates
@@ -230,43 +232,43 @@ Actions: `view` | `create` | `update` | `delete` | `import`.
HTTP mapping:
| Handler | Action |
| ------- | ------ |
| `GET` list / detail | `view` |
| `POST /` create, `POST /plans/generate` | `create` |
| Handler | Action |
| --------------------------------------------------------------------------------------------------- | -------- |
| `GET` list / detail | `view` |
| `POST /` create, `POST /plans/generate` | `create` |
| `PATCH /:id`, `PATCH /:id/status`, `POST /bulk-status`, nested customer contacts, plan destinations | `update` |
| `DELETE /:id`, `POST /bulk-delete` | `delete` |
| `POST /import` | `import` |
| `DELETE /:id`, `POST /bulk-delete` | `delete` |
| `POST /import` | `import` |
Catalog (`GET /privilege-keys`, needs `PRIVILEGES` `view`):
| code | label |
| ---- | ----- |
| `PRIVILEGES` | Privileges |
| `USERS` | Users |
| `CONFIGURATION.DIVISION` | Divisions |
| `CONFIGURATION.BRANCH` | Branches |
| `CONFIGURATION.CUSTOMER` | Customers |
| `CONFIGURATION.EMPLOYEE` | Employees |
| `CONFIGURATION.PRODUCT` | Products |
| `SALES.REQUEST` | Sales requests |
| `SALES.ORDER` | Sales orders |
| `SALES.PACKING_SLIP` | Packing slips |
| `SALES.INVOICE` | Sales invoices |
| `SALES.PAYMENT` | Sales payments |
| `CONFIGURATION.SETTING` | Company settings |
| `SALES.CYCLE` | Sales cycles |
| `SALES.PLAN` | Sales plans |
| `LOGISTICS.CYCLE` | Logistics cycles |
| `LOGISTICS.PLAN` | Logistics plans |
| code | label |
| ------------------------ | ---------------- |
| `PRIVILEGES` | Privileges |
| `USERS` | Users |
| `CONFIGURATION.DIVISION` | Divisions |
| `CONFIGURATION.BRANCH` | Branches |
| `CONFIGURATION.CUSTOMER` | Customers |
| `CONFIGURATION.EMPLOYEE` | Employees |
| `CONFIGURATION.PRODUCT` | Products |
| `SALES.REQUEST` | Sales requests |
| `SALES.ORDER` | Sales orders |
| `SALES.PACKING_SLIP` | Packing slips |
| `SALES.INVOICE` | Sales invoices |
| `SALES.PAYMENT` | Sales payments |
| `CONFIGURATION.SETTING` | Company settings |
| `SALES.CYCLE` | Sales cycles |
| `SALES.PLAN` | Sales plans |
| `LOGISTICS.CYCLE` | Logistics cycles |
| `LOGISTICS.PLAN` | Logistics plans |
### Field purpose
Cycles and plans do **not** use a single key. Privilege is resolved from `purpose`:
| purpose | cycle key | plan key |
| ------- | --------- | -------- |
| `sales` | `SALES.CYCLE` | `SALES.PLAN` |
| purpose | cycle key | plan key |
| ----------- | ----------------- | ---------------- |
| `sales` | `SALES.CYCLE` | `SALES.PLAN` |
| `logistics` | `LOGISTICS.CYCLE` | `LOGISTICS.PLAN` |
`purpose` is read from **body** (writes) or **query** (lists). If omitted, the user may proceed if they have the action on **either** purpose; list results are filtered to purposes they can view. Superadmin bypasses.
@@ -279,17 +281,17 @@ Sales plans may attach `invoiceIds` only. Logistics plans may attach `packingSli
Unless a section says otherwise, each resource below implements:
| Method | Path | Status | Notes |
| ------ | ---- | ------ | ----- |
| `GET` | `/{resource}` | `200` | Paginated `{ data, meta }` |
| `GET` | `/{resource}/:id` | `200` | Detail (may include nested arrays list omits) |
| `POST` | `/{resource}` | `201` | Create |
| `PATCH` | `/{resource}/:id` | `200` | Update — **no** `status` |
| `PATCH` | `/{resource}/:id/status` | `200` | `{ status }` |
| `DELETE` | `/{resource}/:id` | `204` | Hard delete (cycles/plans: archive) |
| `POST` | `/{resource}/bulk-delete` | `200` | `{ ids }``{ deleted }` |
| `POST` | `/{resource}/bulk-status` | `200` | `{ ids, status }``{ updated }` |
| `POST` | `/{resource}/import` | `200` | multipart `file``{ imported }` |
| Method | Path | Status | Notes |
| -------- | ------------------------- | ------ | --------------------------------------------- |
| `GET` | `/{resource}` | `200` | Paginated `{ data, meta }` |
| `GET` | `/{resource}/:id` | `200` | Detail (may include nested arrays list omits) |
| `POST` | `/{resource}` | `201` | Create |
| `PATCH` | `/{resource}/:id` | `200` | Update — **no** `status` |
| `PATCH` | `/{resource}/:id/status` | `200` | `{ status }` |
| `DELETE` | `/{resource}/:id` | `204` | Hard delete (cycles/plans: archive) |
| `POST` | `/{resource}/bulk-delete` | `200` | `{ ids }``{ deleted }` |
| `POST` | `/{resource}/bulk-status` | `200` | `{ ids, status }``{ updated }` |
| `POST` | `/{resource}/import` | `200` | multipart `file``{ imported }` |
Audit fields on primary DTOs: `createdAt`, `updatedAt` (unix ms), `createdBy`, `updatedBy` (user UUID).
@@ -352,9 +354,7 @@ List filters: `name`, `code`, `status`, `search` (name/code).
"name": "Sales Staff",
"code": "SALES_STAFF",
"status": "draft",
"details": [
{ "privilegeKeyId": "uuid", "action": "view", "value": true }
]
"details": [{ "privilegeKeyId": "uuid", "action": "view", "value": true }]
}
```
@@ -467,11 +467,11 @@ Contact create: `name` required; `jobTitle?`, `phone?`, `mobilePhone?`, `notes?`
### Nested contacts (privilege = customer **update**)
| Method | Path | Status | Body | Response |
| ------ | ---- | ------ | ---- | -------- |
| `POST` | `/customers/:id/contacts` | `200` | create contact | full `CustomerDto` |
| `PATCH` | `/customers/:id/contacts/:contactId` | `200` | partial contact | full `CustomerDto` |
| `DELETE` | `/customers/:id/contacts/:contactId` | `204` | — | empty |
| Method | Path | Status | Body | Response |
| -------- | ------------------------------------ | ------ | --------------- | ------------------ |
| `POST` | `/customers/:id/contacts` | `200` | create contact | full `CustomerDto` |
| `PATCH` | `/customers/:id/contacts/:contactId` | `200` | partial contact | full `CustomerDto` |
| `DELETE` | `/customers/:id/contacts/:contactId` | `204` | — | empty |
List filters: `code`, `name`, `phone`, `address`, `nfcId`, `status`, `search` (code/name/address).
@@ -574,12 +574,8 @@ Detail adds:
```json
{
"products": [
{ "id": "uuid", "productId": "uuid", "quantity": "2.0000", "price": "12500.0000" }
],
"images": [
{ "id": "uuid", "url": "https://…", "description": null }
]
"products": [{ "id": "uuid", "productId": "uuid", "quantity": "2.0000", "price": "12500.0000" }],
"images": [{ "id": "uuid", "url": "https://…", "description": null }]
}
```
@@ -659,10 +655,10 @@ CSV required: `date` (allocations empty on import).
Key: `CONFIGURATION.SETTING`. Singleton — no list/CRUD.
| Method | Path | Action | Notes |
| ------ | ---- | ------ | ----- |
| `GET` | `/settings` | view | `404` `{ "message": "Settings not configured" }` if never patched |
| `PATCH` | `/settings` | update | upserts |
| Method | Path | Action | Notes |
| ------- | ----------- | ------ | ----------------------------------------------------------------- |
| `GET` | `/settings` | view | `404` `{ "message": "Settings not configured" }` if never patched |
| `PATCH` | `/settings` | update | upserts |
**Patch body:** `{ "cycleStartDate": "2026-01-05" }` (`YYYY-MM-DD` only).
@@ -730,11 +726,12 @@ No hard delete: `DELETE` / `bulk-delete` archive. **Has CSV import.** Standard l
"endBranchId": "uuid",
"routeGeometry": {
"type": "LineString",
"coordinates": [[106.8456, -6.2088], [107.0, -6.3]]
"coordinates": [
[106.8456, -6.2088],
[107.0, -6.3]
]
},
"destinations": [
{ "id": "uuid", "customerId": "uuid", "sortOrder": 0 }
]
"destinations": [{ "id": "uuid", "customerId": "uuid", "sortOrder": 0 }]
}
],
"status": "draft",
@@ -759,19 +756,19 @@ Privilege: `RequireFieldPrivilege('plan', action)` → `SALES.PLAN` or `LOGISTIC
**No CSV import.** Delete archives. Extra routes: generate, add/remove destinations.
| Method | Path | Action | Status | Body / response |
| ------ | ---- | ------ | ------ | --------------- |
| `GET` | `/plans` | view | 200 | paginated |
| `GET` | `/plans/:id` | view | 200 | `PlanDto` |
| `POST` | `/plans/generate` | create | 200 | `{ employeeId, purpose, from, to }``{ created, skipped }` |
| `POST` | `/plans` | create | 201 | see below |
| `PATCH` | `/plans/:id` | update | 200 | no status |
| `PATCH` | `/plans/:id/status` | update | 200 | `{ status }` |
| `POST` | `/plans/:id/destinations` | update | 200 | `{ customerId, afterDestinationId? }``PlanDto` |
| `DELETE` | `/plans/:id/destinations/:destinationId` | update | **200** | `PlanDto` (not 204) |
| `DELETE` | `/plans/:id` | delete | 204 | archives |
| `POST` | `/plans/bulk-delete` | delete | 200 | `{ deleted }` |
| `POST` | `/plans/bulk-status` | update | 200 | `{ updated }` |
| Method | Path | Action | Status | Body / response |
| -------- | ---------------------------------------- | ------ | ------- | ------------------------------------------------------------ |
| `GET` | `/plans` | view | 200 | paginated |
| `GET` | `/plans/:id` | view | 200 | `PlanDto` |
| `POST` | `/plans/generate` | create | 200 | `{ employeeId, purpose, from, to }``{ created, skipped }` |
| `POST` | `/plans` | create | 201 | see below |
| `PATCH` | `/plans/:id` | update | 200 | no status |
| `PATCH` | `/plans/:id/status` | update | 200 | `{ status }` |
| `POST` | `/plans/:id/destinations` | update | 200 | `{ customerId, afterDestinationId? }``PlanDto` |
| `DELETE` | `/plans/:id/destinations/:destinationId` | update | **200** | `PlanDto` (not 204) |
| `DELETE` | `/plans/:id` | delete | 204 | archives |
| `POST` | `/plans/bulk-delete` | delete | 200 | `{ deleted }` |
| `POST` | `/plans/bulk-status` | update | 200 | `{ updated }` |
Register static paths (`generate`, `bulk-delete`, `bulk-status`) before `:id`.
@@ -898,14 +895,7 @@ type UnixMs = number;
type DecimalString = string;
type CoreStatus = 'draft' | 'active' | 'archived';
type FieldPurpose = 'sales' | 'logistics';
type Weekday =
| 'monday'
| 'tuesday'
| 'wednesday'
| 'thursday'
| 'friday'
| 'saturday'
| 'sunday';
type Weekday = 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday';
type PrivilegeAction = 'view' | 'create' | 'update' | 'delete' | 'import';
type PaginationMeta = {
@@ -932,10 +922,7 @@ type Me = {
username: string;
isSuperadmin: boolean;
privilege: { id: Uuid; name: string; code: string } | null;
permissions: Record<
string,
Record<PrivilegeAction, boolean>
>;
permissions: Record<string, Record<PrivilegeAction, boolean>>;
};
type RouteGeometry = {