# MOT (eSuite Materials Ordering) knowledge graph

MOT (Materials Order Tracking) is the configuration app that materials planners and order operations staff use to set up
what physical test materials a testing program ships, and how customer orders for them are turned into quantities,
batches and exports. Planners keep, per [[test-administration]], an outline of every material (the [[tail]]) with its
print specs and bills of materials. Order staff configure [[distribution]]s (shipping hierarchy, waves, attribute
mapping, quantity rules), load order and student files, and approve, batch and export the orders. Heavy work always
runs as background batch jobs in a separate service.

This graph covers two repos:
- `esuite-mot-configuration-api` (primary; unprefixed `source` paths): Spring Boot 4 / Java 21 REST API, [[mot-config-api]].
- `esuite-mot-configuration` (`source` paths prefixed `esuite-mot-configuration/`): React 18 SPA, [[mot-config-ui]].

## Start here
1. [[mot-config-api]] and [[mot-config-ui]]: the two deployables and how they talk.
2. [[sign-in]] and `permissions.md`: how a user gets in, and why every check is UI-only.
3. [[test-admin-item-list]] (the TAIL) and [[tail-service]]: the core materials data and its API.
4. [[run-batch-request]]: the pattern behind every upload, rollover, approval and report.
5. [[request-processing-service]] and [[mot-request-processing-api]]: where batch jobs are recorded and launched.
6. [[distribution]], [[distribution-rule]] and [[mot-orders-api]]: the orders side, whose service code is not in these repos.
7. [[mot-runtime-config]] and [[mot-config-api-settings]]: every runtime setting for the UI and the API.

## Main flows
- Access: [[sign-in]]
- Materials (TAIL):
  - [[add-item-to-tail]]
  - [[edit-tail-hierarchy]] (parent reconciliation)
  - [[generate-item-numbers]]
  - [[delete-tail-item]]
  - [[add-stock-item]]
  - [[rollover-test-administration]]
- Batch: [[run-batch-request]], [[reprocess-request]]
- Orders:
  - [[rollover-distribution]]
  - [[test-distribution-rules]]
  - [[upload-orders]]
  - [[approve-orders]]
  - [[reprocess-order-errors]]

Product features are `capability` nodes, for example [[tail-maintenance]], [[materials-job-processing]],
[[distribution-rules-editor]] and [[orders-status-batching]]. The glossary is in `concept` nodes: [[tail]],
[[collection-plan]], [[lov]], [[rollover]], [[parent-reconciliation]], [[orders-type]], [[iss-ref]] and [[request-group]].

## Layout
- **API** (`src/main/java/com/pearson/motconfig/`): `controller/` (one `@RestController` per area under `/api/*`),
  `services/`, `repositories/` (Spring Data JPA, mostly `@Query(nativeQuery = true)` SQL), `entities/` (tables, plus
  `Get*` / `*Details` / `*Screen*` classes that are query projections, not tables), `request/` and `response/` DTOs,
  `exception/` (`GlobalExceptionHandler`), `config/` (`CloudConfig` datasource, `S3Helper`), `utilities/MotConfigUtility`
  (mandatory-field checks). Areas: [[tail-service]], [[master-item-service]], [[test-admin-service]],
  [[program-config-service]], [[data-element-lov-service]], [[validation-config-service]],
  [[request-processing-service]], [[user-service]]. DB: [[mot-postgres]] (schemas `config`, `users`, `batch`, and
  `orders`, which the API only reads).
- **UI** (`ui/src/`): `containers/` (one per route, see `Routes.js`), `components/`, `services/` (API calls),
  `hoc/layout.js` + `hoc/helper.js` (role gating), `store/` (Redux Toolkit), `util/fetchToken.js`. Screen groups:
  [[tail-ui]], [[master-item-ui]], [[test-administration-ui]], [[program-configuration-ui]], [[materials-processing-ui]],
  [[users-ui]], [[login-ui]], and on the orders side [[orders-distribution-ui]], [[orders-mapping-ui]],
  [[orders-rules-ui]] and [[orders-processing-ui]] (service layer [[orders-services]]).
- **External services, not in these repos.** The UI calls them directly:
  - [[mot-orders-api]] (`ORDERS_API_URL`): distributions, mapping, rules, orders and errors, about 60 routes.
  - [[mot-tracking-api]] (`TRK_API_URL`): carrier codes only.
  - [[mot-request-processing-api]] (`RP_API_URL` / `request.processing.api.url`): the Spring Batch launcher, also called by the API.

  Their routes are recorded on those integration nodes. Files go to [[mot-s3-bucket]]. Identity comes from [[azure-ad]] (users) and [[aws-cognito]] (API token).
- Build and deploy: [[mot-config-api-deploy]] (Jenkins, Docker, ECR). The UI Dockerfile builds with Vite and serves the result from nginx.

## Conventions for implementers
- **Adding an endpoint.** Add a method to the area's controller (`@RestController`, `@RequestMapping("/api/<area>")`,
  `@CrossOrigin(origins = "*")` like its siblings), validate inputs with a `MotConfigUtility.mandatoryCheck…` helper,
  and put logic in the area's `services/` class. Throw `InvalidInputException` (400), `ResourceNotFoundException` (404)
  or `CustomException` (500). **Throw them outside any `try { … } catch (Exception e)`**, because the house pattern
  re-wraps everything caught as a 500. List GETs throw 404 on empty results, and the UI expects that.
- **SQL.** There are no migrations in these repos (`spring.jpa.hibernate.ddl-auto=none`), so the DDL lives elsewhere.
  New queries go into the area's `repositories/*Repository` as `@Query(value = "…", nativeQuery = true)` with
  schema-qualified tables (`config.test_admin_item_list`, `orders.distribution_rules`). Updates use `@Modifying` and
  `@Transactional`. Read shapes are interface or class projections in `entities/` (`Get*`). Display values are joined in
  SQL from `config.data_element_lov`.
- **Transactions.** Most multi-step writes are not transactional. Only `TAILService.deleteFilteredItems` and
  `MasterItemService.addItem` are. The UI also orchestrates multi-call writes (row shifts, CP rows, stock items). Make
  new multi-step writes a single `@Transactional` service method.
- **Batch jobs.** Don't run long work in a request. Add a row to `batch.request` (seeded outside these repos) for
  the job name and group. The UI looks up `requestId` by `requestName` (`requestGroupName(...)`), then
  `POST /api/batch/postRequestProcessing` with job inputs as a JSON string in `inputParms`. The API inserts
  [[request-processing]] and POSTs `{name, jobParameters:{requestProcessingId}}` to
  `request.processing.api.url/postSpringBatchJob`. The job reports back with `PUT /api/batch/putRequestProcessing`.
  Upload files first with `POST /api/batch/uploadFileToS3`.
- **Runtime config.** API: an external `application.properties` is rendered at container start by
  `envsubst/startup.sh` (DB, `request.processing.api.url`, `cloud.aws.s3Bucket`), and `SPRING_PROFILES_ACTIVE` is
  required. UI: `window.__RUNTIME_CONFIG__` comes from `runtime-env-cra` at container start, so there is no build-time
  env. Add new keys to `ui/.env`, `docker-compose.yml` and the deployment manifest. Everything in it is public.
- **UI calls.** Config-API calls in `services/fetch*.js` / `post*.js` / `put*.js` use bare `axios` with no token.
  `services/interceptor` `apiClient` (orders, plus `rpApiRequest`, `validateRules`, `trackingRequest`) adds the bearer
  token. Gate a new screen by adding its route to a group in `hoc/helper.js` and wrapping the container in `Layout`.
- **Tests.** The API has no `src/test`. The UI uses Vitest (`npm test` = `vitest run`), and the only suites are
  `src/tests/auth/layoutRouteGuard.test.js` and `appRoleAssignment.test.js`, which the Docker build runs.

## Known risks
Each risk below was checked against the code.
- **No authentication or authorization on the API.** [[mot-config-api]] has no Spring Security dependency, filter or
  token check. Every controller is `@CrossOrigin(origins = "*")`. Anyone who can reach it can read and change all
  configuration. Role checks exist only in the UI (`permissions.md`).
- **Users created from the request body.** `POST /api/user/postUserDetails` and `PUT /api/user/putUserDetails`
  ([[user-api]]) take `roleIds` straight from the body and write [[user-role]], so any caller can create an admin.
- **`OAUTH_CLIENT_SECRET` ships to the browser.** `util/fetchToken.js` reads it from `window.__RUNTIME_CONFIG__` and
  runs a `client_credentials` grant in the browser ([[aws-cognito]]). Anyone who loads the page can mint the same bearer
  tokens the app sends to the orders, tracking and request-processing services.
- **`ui/.env` is served publicly.** `ui/Dockerfile` copies `ui/.env` to `/usr/share/nginx/html/.env`, and
  `nginx/default.conf` serves that root with no dotfile rule. Its keys include `OAUTH_CLIENT_SECRET`.
- **Most config calls send no token.** `services/fetchTail.js`, `putTail.js`, `postTail.js` and the other
  `fetch*/post*/put*` files use bare `axios`. That works only because the API ignores tokens.
- **Errors re-wrapped as 500.** Controllers throw `InvalidInputException` or `ResourceNotFoundException` inside
  `try { … } catch (Exception e) { throw new CustomException(...) }`, so many 400s and 404s reach clients as 500s with
  the original message embedded (for example `UserManagementController.getUsers`, `TAILController.getTail`).
- **S3 bucket override.** `RequestProcessingController` `uploadFileToS3` and `downloadFileFromS3` test
  `bucketName == null || bucketName == "" || bucketName.length() > 0`, which is always true. The configured
  `cloud.aws.s3Bucket` is therefore always replaced by `mot-<SPRING_PROFILES_ACTIVE>-st-resources` ([[mot-s3-bucket]]).
- **Non-atomic add-stock-item.** [[add-stock-item]] commits the master item, then makes four more client-side calls
  whose failures are swallowed. The item can exist without its TAIL row while the user sees success. An empty stock
  TAIL makes the add fail every time. [[add-item-to-tail]], [[edit-tail-hierarchy]] and [[generate-item-numbers]] have
  the same partial-write shape.
