Local remote renames (URLs unchanged): backend-v4 → origin-backend (canonical backend) frontend-v4 → origin-frontend (canonical frontend) origin → mirror-monorepo (secondary full-tree mirror) The `v4` branch auto-updated to track `mirror-monorepo/v4`. Docs: - Header banner: new 2026-04-19 "remote rename" entry; release entry now references `mirror-monorepo/v4` instead of `origin/v4`. - §6.2 remotes table updated with the new names (plus a rename-history note) and reordered so the two canonical repos lead. - §6.3 feature workflow now uses `git pull/push mirror-monorepo v4`. - §6.4 publish commands now use `origin-backend` / `origin-frontend`. Made-with: Cursor
1218 lines
71 KiB
Markdown
1218 lines
71 KiB
Markdown
# EnCoach Platform — Project Summary
|
||
|
||
> Last updated: 2026-04-19 | **Canonical repos: [`encoach_backend_v4`](https://git.albousalh.com/devops/encoach_backend_v4) (backend) + [`encoach_frontend_v4`](https://git.albousalh.com/devops/encoach_frontend_v4) (frontend), branch `main`.**
|
||
>
|
||
> This workspace (`odoo19/`) is a **developer monorepo / working tree only** — it conveniently contains both halves side-by-side for local development and testing. The two split repos above are the **authoritative origins** for each half: every change must be published to them (via `git subtree split + push`) before the team lead can deploy. See **§6 Git Remotes & Repositories** for the exact workflow.
|
||
|
||
> **Latest events:**
|
||
> - **2026-04-19 (remote rename):** Aligned local remote names with the new doctrine — `backend-v4 → origin-backend`, `frontend-v4 → origin-frontend`, `origin → mirror-monorepo`. The `v4` branch now tracks `mirror-monorepo/v4`. All publishing commands in §6 updated. No remote URLs changed.
|
||
> - **2026-04-19 (repos-of-record reorganization):** Declared `encoach_backend_v4` and `encoach_frontend_v4` as the canonical origins for their respective halves; the monorepo `encoach_backend_new_v2/v4` is now a secondary working-tree mirror kept only for history/convenience. §6 rewritten around this model.
|
||
> - **2026-04-19 (release to VPS repos):** Pushed the accumulated institutional + support + training work. Frontend canonical → `encoach_frontend_v4/main` (`b78124bb..435930a8`, 403 files, clean fast-forward). Backend canonical → `encoach_backend_v4/main` (`74d83af5..6ec68160`, 420 files, clean fast-forward). Monorepo mirror → `mirror-monorepo/v4` (`98b9837a`). `.gitignore` now excludes `pgdata_bak_*/`, `frontend/.vite/`, `frontend/dist/`, `frontend/node_modules/` so the 674 MB of local pgdata backups never leak into a push. See §6.
|
||
> - **2026-04-18 (training section):** Built the Training section end-to-end — the `Vocabulary` and `Grammar` pages (previously pure hard-coded mocks) are now wired to real Odoo data with CRUD, search, level filtering, and per-user progress tracking. New `encoach.vocab.item` / `encoach.vocab.progress` / `encoach.grammar.rule` / `encoach.grammar.progress` models + `/api/training/vocabulary` and `/api/training/grammar` controllers. 26/26 API write-flow actions passing (`test_training_flows.py`); both pages verified live in-browser. See §19.
|
||
> - **2026-04-18 (support section):** Built the Support section end-to-end — the `Tickets`, `Payment Record`, and `Settings` pages are now wired to real Odoo data (they were previously mocks/404s). New `encoach.ticket` model + full CRUD controller, `/api/payment-records` deriving from `op.student.fees.details` + `account.move`, `/api/codes` + `/api/packages` + `/api/grading-config` powering the Settings tabs. 29/29 API write-flow actions passing (`test_support_flows.py`); all 3 pages verified live in-browser. See §18.
|
||
> - **2026-04-18 (institutional pages write-flow):** API-level write-flow sweep across all 14 institutional admin pages — **44/44 actions passing** (CREATE / EDIT / DELETE / WORKFLOW). Numerous backend gaps fixed: missing DELETE routes added (admission-registers, admissions, result-templates, student-leaves), lesson/library detail endpoints added, custom `encoach.asset` model introduced to replace the missing `op.asset`, marksheet generate endpoint aliased for Odoo 19's `generate_result`, fees `create-invoice` auto-wires the product income account via Odoo 19's JSONB property storage, academic-year DELETE now cascades dependent terms. See §17.
|
||
> - **2026-04-18 (browser E2E):** Full end-to-end test across admin, teacher, student user types. All critical pages verified working in browser. Two backend bugs fixed: (1) `/api/approval-workflows` returned 500 because raw SQL referenced a table that doesn't exist — now gracefully returns empty list; (2) `DELETE /api/courses/<id>` returned opaque 500 when course had enrollments — now returns 409 with a clear message and supports `?force=true` for cascade-delete. See §16.
|
||
|
||
---
|
||
|
||
## 1. Project Overview
|
||
|
||
EnCoach is an AI-powered online learning and examination platform built on **Odoo 19** (backend) and **React + TypeScript + Vite** (frontend). It supports IELTS and general English exam generation, student exam sessions, auto-scoring, adaptive learning, and course creation.
|
||
|
||
---
|
||
|
||
## 2. Tech Stack
|
||
|
||
| Layer | Technology |
|
||
|-------|-----------|
|
||
| **Backend** | Odoo 19, Python 3.12, PostgreSQL 18 |
|
||
| **Frontend** | React 18, TypeScript, Vite 5, Tailwind CSS, Shadcn UI |
|
||
| **State Management** | `@tanstack/react-query` (mutations + queries) |
|
||
| **Auth** | JWT (issued by Odoo, validated via `validate_token` helper) |
|
||
| **AI** | OpenAI API (with fallback templates when API key not configured) |
|
||
| **Database** | `encoach_v2` (PostgreSQL 18.3) |
|
||
|
||
---
|
||
|
||
## 3. User Credentials
|
||
|
||
Admin/teacher users use password **`admin`**. Student passwords were reset to **`student123`** during end-to-end testing.
|
||
|
||
| ID | Login | Name | Type | Password |
|
||
|----|-------|------|------|----------|
|
||
| 2 | `admin` | Administrator | superadmin | `admin` |
|
||
| 5 | `admin@encoach.test` | Admin User | admin | `admin` |
|
||
| 6 | `sarah@encoach.test` | Sarah Ahmed | student | **`student123`** |
|
||
| 7 | `omar@encoach.test` | Omar Khan | student | **`student123`** |
|
||
| 8 | `layla@encoach.test` | Layla Nasser | student | **`student123`** |
|
||
| 9 | `khalid@encoach.test` | Dr. Khalid | teacher | `admin` |
|
||
| 10 | `fatima@encoach.test` | Ms. Fatima | teacher | `admin` |
|
||
|
||
> **Note:** Student passwords were bulk-reset to `student123` via a direct `psycopg2` + `passlib` script during testing. If a student can't login, reset their password in psql using `passlib.context.CryptContext(['pbkdf2_sha512'])`.
|
||
|
||
### Login API
|
||
|
||
```bash
|
||
# Admin login
|
||
curl -X POST http://localhost:8069/api/login \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"email":"admin","password":"admin"}'
|
||
|
||
# Student login
|
||
curl -X POST http://localhost:8069/api/login \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"email":"sarah@encoach.test","password":"student123"}'
|
||
```
|
||
|
||
Returns `{ token, user, permissions }`. The `token` (JWT) is used as `Authorization: Bearer <token>` for all subsequent API calls.
|
||
|
||
---
|
||
|
||
## 4. How to Start the Project
|
||
|
||
### Prerequisites
|
||
- PostgreSQL 18 binary at `/Users/yamenahmad/micromamba/envs/odoo19/bin/`
|
||
- Python 3.12 (conda env at `.conda-envs/odoo19/` or micromamba)
|
||
- Node.js 20 at `/Users/yamenahmad/.local/node/bin/`
|
||
|
||
### Start PostgreSQL
|
||
|
||
```bash
|
||
PGBIN="/Users/yamenahmad/micromamba/envs/odoo19/bin"
|
||
PGDATA="/Users/yamenahmad/projects2026/odoo/odoo19/pgdata"
|
||
"$PGBIN/pg_ctl" -D "$PGDATA" -l "$PGDATA/logfile" start
|
||
```
|
||
|
||
> **Important:** The pgdata was initialized with PG 18. The conda env has PG 17.6, which is **incompatible**. Always use the micromamba PG 18 binary above.
|
||
>
|
||
> If PG fails with config errors, check `pgdata/logfile`. The parameter `autovacuum_worker_slots` (PG 18-only) was commented out in `pgdata/postgresql.conf` line 687 for compatibility.
|
||
|
||
### Start Odoo Backend
|
||
|
||
```bash
|
||
cd /Users/yamenahmad/projects2026/odoo/odoo19
|
||
./run.sh
|
||
```
|
||
|
||
Runs at **http://127.0.0.1:8069**
|
||
|
||
> **Odoo config** (`odoo.conf`) must have `db_name = encoach_v2` and `dbfilter = ^encoach_v2$`, otherwise API routes return 404 ("No database selected").
|
||
|
||
### Start Frontend
|
||
|
||
```bash
|
||
export PATH="/Users/yamenahmad/.local/node/bin:$PATH"
|
||
cd /Users/yamenahmad/projects2026/odoo/odoo19/frontend
|
||
npm run dev
|
||
```
|
||
|
||
Runs at **http://localhost:8080** — Vite proxies `/api` requests to Odoo `:8069`.
|
||
|
||
### Stop PostgreSQL
|
||
|
||
```bash
|
||
"$PGBIN/pg_ctl" -D "$PGDATA" stop
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Project Structure
|
||
|
||
```
|
||
odoo19/
|
||
├── backend/
|
||
│ ├── custom_addons/ # All EnCoach Odoo modules
|
||
│ │ ├── encoach_api/ # Auth (login/logout/user) + base helpers
|
||
│ │ ├── encoach_ai/ # AI generation, exam generation/submit
|
||
│ │ ├── encoach_ai_course/ # AI course creation pipelines
|
||
│ │ ├── encoach_adaptive/ # Adaptive learning engine
|
||
│ │ ├── encoach_core/ # Core models (user, entity, role, permission)
|
||
│ │ ├── encoach_exam_template/ # Exam CRUD, sessions, rubrics, assignments, schedules
|
||
│ │ ├── encoach_scoring/ # Grading queue, score release
|
||
│ │ ├── encoach_taxonomy/ # Subject/topic tree
|
||
│ │ ├── encoach_lms_api/ # LMS: courses, chapters, materials, progress, enrollment
|
||
│ │ ├── encoach_course_gen/ # Course generation
|
||
│ │ ├── encoach_branding/ # Entity branding
|
||
│ │ ├── encoach_signup/ # Registration, email verification, onboarding
|
||
│ │ ├── encoach_placement/ # Placement test
|
||
│ │ └── ...
|
||
│ └── openeducat_erp-19.0/ # OpenEduCat base modules
|
||
├── frontend/
|
||
│ ├── src/
|
||
│ │ ├── pages/ # Route pages (admin/, student/, etc.)
|
||
│ │ ├── components/ # Reusable UI components
|
||
│ │ ├── services/ # API service classes
|
||
│ │ ├── hooks/ # React Query hooks
|
||
│ │ ├── lib/ # api-client, utils
|
||
│ │ └── types/ # TypeScript type definitions
|
||
│ ├── .env.development # VITE_API_BASE_URL=/api
|
||
│ └── .env.production # VITE_API_BASE_URL=https://api.encoach.com/api
|
||
├── docs/ # Documentation
|
||
├── odoo/ # Odoo 19 source code
|
||
├── odoo.conf # Odoo configuration
|
||
├── pgdata/ # PostgreSQL data directory (PG 18)
|
||
├── run.sh # Start Odoo
|
||
├── start.sh # Start PG + Odoo
|
||
└── stop.sh # Stop services
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Git Remotes & Repositories
|
||
|
||
> **Canonical (repos-of-record):**
|
||
> - Backend → **[`encoach_backend_v4`](https://git.albousalh.com/devops/encoach_backend_v4.git)** — branch `main`
|
||
> - Frontend → **[`encoach_frontend_v4`](https://git.albousalh.com/devops/encoach_frontend_v4.git)** — branch `main`
|
||
>
|
||
> These are the repositories the team lead (Talal / devops) clones on the VPS and
|
||
> builds with `docker compose up -d --build`. **All production / staging code
|
||
> lives here.**
|
||
>
|
||
> This workspace (`odoo19/`) is a **developer monorepo / working tree** — a
|
||
> convenience folder that holds both halves side-by-side so a single developer
|
||
> can run the full stack locally. It is **not** the source of truth: every change
|
||
> must be published to the two canonical repos above via `git subtree split + push`
|
||
> before it counts as merged.
|
||
|
||
### 6.1 Repository Layout
|
||
|
||
```
|
||
odoo19/ ← developer working tree (NOT source of truth)
|
||
├── backend/ → published to encoach_backend_v4/main (authoritative)
|
||
│ ├── custom_addons/
|
||
│ ├── Dockerfile
|
||
│ ├── docker-compose.yml
|
||
│ └── requirements.txt
|
||
├── frontend/ → published to encoach_frontend_v4/main (authoritative)
|
||
│ ├── src/
|
||
│ ├── public/
|
||
│ ├── Dockerfile, vite.config.ts, package.json, …
|
||
└── docs/ → lives only in this workspace
|
||
```
|
||
|
||
### 6.2 Remotes Configured on This Clone
|
||
|
||
Listed in order of importance. The two bold rows are the **repos of record**;
|
||
everything else is a secondary mirror or legacy.
|
||
|
||
| Remote | URL | Role |
|
||
|--------|-----|------|
|
||
| **`origin-backend`** | `https://git.albousalh.com/devops/encoach_backend_v4.git` | **Canonical backend** — branch `main`. VPS deploy target. |
|
||
| **`origin-frontend`** | `https://git.albousalh.com/devops/encoach_frontend_v4.git` | **Canonical frontend** — branch `main`. VPS deploy target. |
|
||
| `mirror-monorepo` | `https://git.albousalh.com/devops/encoach_backend_new_v2.git` | Secondary full-tree mirror (branch `v4` tracked here). Useful for history/backup and for docs that live only in the workspace root (e.g. this file). **Not** the source of truth. |
|
||
| `ip-origin` | `https://5.189.151.117/devops/encoach_backend_new_v2.git` | IP fallback for `mirror-monorepo` when DNS is flaky. |
|
||
| `backend-v3` | `https://git.albousalh.com/devops/encoach_backend_new_v3.git` | Legacy v3 monorepo mirror — rarely used. |
|
||
| `frontend-v3` | `https://git.albousalh.com/devops/encoach_frontend_new_v3.git` | Legacy v3 frontend mirror — rarely used. |
|
||
| `frontend` | `https://git.albousalh.com/devops/encoach_frontend_new_v2.git` | Legacy v2 frontend monorepo — rarely used. |
|
||
|
||
> Rename history: `backend-v4 → origin-backend`, `frontend-v4 → origin-frontend`,
|
||
> `origin → mirror-monorepo` (applied 2026-04-19). If you have old scripts or
|
||
> notes referencing the old names, update them or add aliases via
|
||
> `git remote rename`.
|
||
|
||
### 6.3 Feature Development Workflow
|
||
|
||
Everything is edited inside this workspace, but a change is only **merged**
|
||
once it lands on `main` of the two canonical repos.
|
||
|
||
```bash
|
||
# 1. Start from a clean workspace synced with the monorepo mirror
|
||
git checkout v4
|
||
git pull mirror-monorepo v4 # optional: refresh full-tree mirror
|
||
|
||
# 2. Create a feature branch
|
||
git checkout -b feature/<short-name>
|
||
|
||
# 3. Make changes, commit small and often
|
||
git add <files>
|
||
git commit -m "feat(<area>): <short description>"
|
||
|
||
# 4. Merge back onto v4 (or open a PR if branch protection is enabled)
|
||
git checkout v4
|
||
git merge --no-ff feature/<short-name>
|
||
|
||
# 5. Publish to the CANONICAL repos (mandatory — see §6.4)
|
||
# This is what the team lead / CI will see on the VPS.
|
||
|
||
# 6. (Optional) push the monorepo mirror for a full-tree backup / docs
|
||
git push mirror-monorepo v4
|
||
```
|
||
|
||
### 6.4 Publishing to the Canonical Repos (MANDATORY)
|
||
|
||
The canonical repos expect a **flat tree** (no `backend/` or `frontend/`
|
||
prefix). Use `git subtree split` to carve each subdirectory into its own
|
||
parallel history, then push to `main`. These commands produce clean
|
||
fast-forwards when nothing has been pushed directly to the canonical repos
|
||
from elsewhere — which is the invariant we enforce.
|
||
|
||
```bash
|
||
# ── Publish BACKEND → encoach_backend_v4 ─────────────────────────────────
|
||
git subtree split --prefix=backend HEAD -b split-backend-v4
|
||
git push origin-backend split-backend-v4:main
|
||
git branch -D split-backend-v4 # cleanup the temp branch
|
||
|
||
# ── Publish FRONTEND → encoach_frontend_v4 ───────────────────────────────
|
||
git subtree split --prefix=frontend HEAD -b split-frontend-v4
|
||
git push origin-frontend split-frontend-v4:main
|
||
git branch -D split-frontend-v4
|
||
```
|
||
|
||
If a push is rejected as non-fast-forward, fetch and inspect before forcing:
|
||
|
||
```bash
|
||
git fetch origin-backend origin-frontend
|
||
git log --oneline origin-backend/main..split-backend-v4 # what we're adding
|
||
git log --oneline split-backend-v4..origin-backend/main # divergence, if any
|
||
```
|
||
|
||
Only use `--force` after confirming the remote `main` has no commits worth
|
||
keeping (e.g. someone pushed directly and it must be overwritten). Since the
|
||
canonical repos are the source of truth, a force-push there is a **destructive
|
||
production operation** — pair it with a Slack heads-up to the team lead.
|
||
|
||
### 6.5 What Each Canonical Repo Contains
|
||
|
||
**`encoach_backend_v4` (branch `main`)** — everything under `backend/` hoisted
|
||
to repo root:
|
||
|
||
- `custom_addons/` (all EnCoach Odoo addons + `encoach_lms_api`)
|
||
- `Dockerfile`, `docker-compose.yml`, `requirements.txt`
|
||
|
||
**Not shipped:** `openeducat_erp-19.0/` is vendored locally but not tracked in
|
||
git. The VPS needs OpenEduCat installed separately — either baked into the
|
||
container image, mounted at `/opt/odoo/extra_addons`, or installed via pip if a
|
||
package release is used. `backend/odoo.conf` is also not currently tracked; add
|
||
it in a follow-up commit if the VPS container needs a baked-in config.
|
||
|
||
**`encoach_frontend_v4` (branch `main`)** — everything under `frontend/` hoisted
|
||
to repo root:
|
||
|
||
- `src/`, `public/`, `docs/` (frontend docs only)
|
||
- `package.json`, `package-lock.json`, `bun.lock`, `bun.lockb`
|
||
- `Dockerfile`, `docker-compose.yml`
|
||
- `vite.config.ts`, `tsconfig.*.json`, `tailwind.config.ts`, `eslint.config.js`
|
||
- `index.html`, `components.json`, `.env.example`
|
||
|
||
**Not shipped** (ignored by `frontend/.gitignore`): `node_modules/`, `dist/`,
|
||
`.vite/`, `.env`, `.env.development`, `.env.production`.
|
||
|
||
### 6.6 VPS Deploy — Team-Lead Quickstart
|
||
|
||
```bash
|
||
# Backend
|
||
git clone https://git.albousalh.com/devops/encoach_backend_v4.git
|
||
cd encoach_backend_v4 && docker compose up -d --build
|
||
|
||
# Frontend
|
||
git clone https://git.albousalh.com/devops/encoach_frontend_v4.git
|
||
cd encoach_frontend_v4 && cp .env.example .env # set VITE_API_BASE_URL
|
||
docker compose up -d --build # or: npm ci && npm run build
|
||
```
|
||
|
||
### 6.7 Safety Rules (DO NOT COMMIT)
|
||
|
||
Already excluded via `.gitignore` — do not force them in:
|
||
|
||
- `pgdata/`, `pgdata_bak_*/` — PostgreSQL data directories (up to ~700 MB)
|
||
- `frontend/node_modules/`, `frontend/dist/`, `frontend/.vite/` — build artifacts
|
||
- `.env`, `.env.development`, `.env.production` — local secrets
|
||
- `.venv/`, `venv/`, `.conda-envs/` — Python virtualenvs
|
||
- `*.zip`, `*.tar.gz` — archives
|
||
|
||
`odoo.conf` at the workspace root IS tracked (local Mac dev config, harmless).
|
||
`backend/odoo.conf` (container-friendly) is **not** tracked; add it in a
|
||
follow-up if the VPS image needs it baked in.
|
||
|
||
### 6.8 Git Credentials
|
||
|
||
Stored in macOS Keychain for `git.albousalh.com`:
|
||
- Username: `yamen`
|
||
- Auth: token-based (stored in keychain, not in this file)
|
||
|
||
---
|
||
|
||
## 7. API Routes Reference
|
||
|
||
### Authentication (`encoach_api`)
|
||
| Method | Route | Auth | Description |
|
||
|--------|-------|------|-------------|
|
||
| POST | `/api/login` | public | Login, returns JWT |
|
||
| GET | `/api/user` | JWT | Get current user |
|
||
| POST | `/api/logout` | public | Logout (client clears token) |
|
||
|
||
### Exam Generation & AI (`encoach_ai`)
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| POST | `/api/exam/<module>/generate` | AI-generate content for a module (reading/listening/writing/speaking) |
|
||
| POST | `/api/exam/generation/submit` | Submit generated exam, create questions |
|
||
| POST | `/api/exam/<module>/generate/save` | Save generated content |
|
||
|
||
### Exam Session (`encoach_exam_template`)
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET | `/api/exam/<exam_id>/session` | Get exam session (creates attempt if needed) |
|
||
| POST | `/api/exam/<exam_id>/autosave` | Autosave student answers |
|
||
| POST | `/api/exam/<exam_id>/submit` | Submit exam for scoring |
|
||
| GET | `/api/exam/<exam_id>/status` | Get attempt status |
|
||
| GET | `/api/exam/<exam_id>/results` | Get exam results |
|
||
|
||
### Custom Exams
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET | `/api/exam/custom/list` | List custom exams |
|
||
| POST | `/api/exam/custom/create` | Create custom exam |
|
||
| GET/PUT/DELETE | `/api/exam/custom/<id>` | CRUD on custom exam |
|
||
|
||
### Exam Structures, Templates, Schedules, Assignments
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET/POST | `/api/exam-structures` | List/create exam structures |
|
||
| GET/POST | `/api/exam/templates` | List/create exam templates |
|
||
| GET/POST | `/api/exam-schedules` | List/create schedules |
|
||
| GET | `/api/student/my-exams` | Student's assigned exams |
|
||
| GET/POST | `/api/assignments` | List/create assignments |
|
||
|
||
### Rubrics & Approval Workflows
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET/POST | `/api/rubrics` | List/create rubrics |
|
||
| GET/POST | `/api/rubric-groups` | List/create rubric groups |
|
||
| GET/POST | `/api/approval-workflows` | List/create approval workflows |
|
||
| POST | `/api/approval-requests/<id>/approve` | Approve request |
|
||
| POST | `/api/approval-requests/<id>/reject` | Reject request |
|
||
|
||
### Entities
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET | `/api/entities` | List entities |
|
||
|
||
### LMS Core (`encoach_lms_api` — `lms_core.py`)
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET/POST | `/api/courses` | List all courses (paginated) / create course |
|
||
| GET/PUT/DELETE | `/api/courses/<id>` | Get / update / delete a single course |
|
||
| GET/POST | `/api/students` | List students / create student |
|
||
| GET/PUT/DELETE | `/api/students/<id>` | Get / update / delete a student |
|
||
| GET | `/api/student/my-courses` | Enrolled courses for logged-in student (with progress) |
|
||
| POST | `/api/students/<id>/enroll` | Enroll a student in one or more courses |
|
||
| POST | `/api/courses/<id>/enroll` | Bulk-enroll students in a course |
|
||
| GET/POST | `/api/teachers` | List / create teachers |
|
||
| GET/PUT/DELETE | `/api/teachers/<id>` | Get / update / delete a teacher |
|
||
| GET/POST | `/api/batches` | List / create batches |
|
||
| GET/PUT/DELETE | `/api/batches/<id>` | Get / update / delete a batch |
|
||
|
||
### LMS Courseware (`encoach_lms_api` — `courseware.py`)
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET/POST | `/api/courses/<id>/chapters` | List / create chapters for a course |
|
||
| GET/PUT/DELETE | `/api/chapters/<id>` | Get / update / delete a chapter |
|
||
| POST | `/api/chapters/<id>/materials` | Upload material (JSON or FormData) |
|
||
| GET/PUT/DELETE | `/api/materials/<id>` | Get / update / delete a material |
|
||
| GET | `/api/materials/<id>/download` | Download material file |
|
||
| GET | `/api/materials/<id>/content` | Serve material file inline (for iframe embed, accepts ?token= param) |
|
||
| POST | `/api/materials/<id>/view` | Mark material as viewed |
|
||
| POST | `/api/chapters/<id>/progress/complete` | Mark chapter as completed |
|
||
| GET | `/api/courses/<id>/completion` | Get course completion status (% progress, post-test availability) |
|
||
| GET | `/api/materials/search` | Search all materials across courses (for Admin Resources page) |
|
||
|
||
### Institutional (`encoach_lms_api`)
|
||
|
||
These power the 14 institutional admin pages under `/admin/*` (see §14) and back the write-flow test in §17. Every route is JWT-protected.
|
||
|
||
**Academic & Departments**
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET / POST | `/api/academic-years` | List / create academic years |
|
||
| GET / PATCH / DELETE | `/api/academic-years/<yid>` | Get / update / delete year (DELETE cascades terms) |
|
||
| POST | `/api/academic-years/<yid>/generate-terms` | Auto-create terms spanning the year (`two_sem`, `three_trim`, `four_qtr`) |
|
||
| GET / POST | `/api/academic-terms` | List / create academic terms |
|
||
| GET / PATCH / DELETE | `/api/academic-terms/<tid>` | Term CRUD |
|
||
| GET / POST | `/api/departments` | List / create departments |
|
||
| GET / PATCH / DELETE | `/api/departments/<did>` | Department CRUD |
|
||
|
||
**Admissions**
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET / POST | `/api/admission-registers` | List / create admission registers |
|
||
| GET / PATCH / DELETE | `/api/admission-registers/<rid>` | Register CRUD (DELETE supported) |
|
||
| POST | `/api/admission-registers/<rid>/confirm` | Confirm register |
|
||
| GET / POST | `/api/admissions` | List / create admissions (application_date **must** fall inside register window) |
|
||
| GET / PATCH / DELETE | `/api/admissions/<aid>` | Admission CRUD (DELETE supported) |
|
||
|
||
**Institutional Exams & Marksheets**
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET / POST | `/api/inst-exam-sessions` | List / create exam sessions |
|
||
| GET / PATCH / DELETE | `/api/inst-exam-sessions/<sid>` | Session CRUD |
|
||
| POST | `/api/inst-exam-sessions/<sid>/schedule` | Schedule session |
|
||
| GET / POST | `/api/result-templates` | List / create result templates |
|
||
| GET / PATCH / DELETE | `/api/result-templates/<tid>` | Template CRUD (DELETE cascades `op.marksheet.register`) |
|
||
| POST | `/api/result-templates/<tid>/generate` (alias: `/generate-marksheets`) | Generate marksheet registers (tries `generate_result` then `action_generate_marksheet`) |
|
||
| GET | `/api/marksheets` | List marksheet registers |
|
||
| POST | `/api/marksheets/<mid>/validate` | Validate marksheet |
|
||
|
||
**Facilities, Assets, Activities**
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET / POST | `/api/facilities` | List / create facilities |
|
||
| GET / PATCH / DELETE | `/api/facilities/<fid>` | Facility CRUD |
|
||
| GET / POST | `/api/assets` | List / create assets (backed by custom `encoach.asset` model) |
|
||
| GET / PATCH / DELETE | `/api/assets/<aid>` | Asset CRUD |
|
||
| GET / POST / PATCH / DELETE | `/api/activity-types` + `/<id>` | Activity-type CRUD |
|
||
| GET / POST / PATCH / DELETE | `/api/activities` + `/<id>` | Activity CRUD |
|
||
|
||
**Library, Lessons, Gradebook, Leaves, Fees**
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET / POST | `/api/library/media` | List / create media items (authors split into `author_ids`) |
|
||
| GET / PATCH / DELETE | `/api/library/media/<mid>` | Media CRUD — detail endpoint returns `author_names` + `author_ids` |
|
||
| GET / POST | `/api/lessons` | List / create lessons |
|
||
| GET / PATCH / DELETE | `/api/lessons/<lid>` | Lesson CRUD — detail endpoint preserves `course_id` / `batch_id` on edits |
|
||
| GET / POST / DELETE | `/api/grading-assignments` + `/<id>` | Grading assignment CRUD (auto-resolves `assignment_type` and default `faculty_id`) |
|
||
| GET | `/api/gradebooks` + `/api/gradebook-lines?gradebook_id=<id>` | Gradebook drill-down |
|
||
| GET | `/api/student-progress` + `/<id>` | Student progress list/detail |
|
||
| GET / POST | `/api/student-leaves` | List / create leave requests |
|
||
| POST | `/api/student-leaves/<lid>/approve` | Approve leave |
|
||
| DELETE | `/api/student-leaves/<lid>` | Delete leave |
|
||
| GET / POST | `/api/fees-plans` | List / create fee plans |
|
||
| POST | `/api/fees-plans/<fid>/create-invoice` | Generate accounting invoice (auto-wires income account on the product template for Odoo 19 JSONB property storage if missing) |
|
||
| POST | `/api/fees-plans/<fid>/register-payment` | Register a payment against the invoice |
|
||
|
||
### Other
|
||
| Route | Description |
|
||
|-------|-------------|
|
||
| `/api/taxonomy/*` | Subject/topic taxonomy tree |
|
||
| `/api/adaptive/*` | Adaptive learning dashboard/signals |
|
||
| `/api/placement/*` | Placement test start/answer/results |
|
||
| `/api/grading/*` | Grading queue, AI suggest |
|
||
| `/api/scores/*` | Score release/reject |
|
||
| `/api/course/*` | Course CRUD, gap analysis, auto-generate |
|
||
| `/api/ai-course/*` | AI course creation pipelines |
|
||
| `/api/auth/register` | User registration |
|
||
|
||
---
|
||
|
||
## 8. Key Odoo Models
|
||
|
||
| Model | Table | Description |
|
||
|-------|-------|-------------|
|
||
| `encoach.exam.custom` | `encoach_exam_custom` | Custom exams (16 records) |
|
||
| `encoach.exam.custom.section` | `encoach_exam_custom_section` | Exam sections (reading/listening/writing/speaking) |
|
||
| `encoach.question` | `encoach_question` | Questions (38 records) |
|
||
| `encoach.student.attempt` | `encoach_student_attempt` | Student exam attempts (10 records) |
|
||
| `encoach.student.answer` | `encoach_student_answer` | Individual answers |
|
||
| `encoach.entity` | `encoach_entity` | Organizations (1: "EnCoach Demo Academy") |
|
||
| `encoach.rubric` | `encoach_rubric` | Grading rubrics |
|
||
| `encoach.exam.template` | `encoach_exam_template` | Exam templates |
|
||
| `encoach.exam.structure` | `encoach_exam_structure` | Exam structures |
|
||
| `encoach.exam.schedule` | `encoach_exam_schedule` | Exam schedules |
|
||
| `encoach.exam.assignment` | `encoach_exam_assignment` | Exam assignments |
|
||
| `encoach.course.chapter` | `encoach_course_chapter` | Course chapters (ordered by sequence) |
|
||
| `encoach.chapter.material` | `encoach_chapter_material` | Materials within chapters (pdf, video, article, link) |
|
||
| `encoach.chapter.progress` | `encoach_chapter_progress` | Per-student chapter progress tracking |
|
||
| `encoach.course.completion` | `encoach_course_completion` | Per-student course completion (% progress, post-test flag) |
|
||
| `encoach.resource` | `encoach_resource` | Learning resources linked to taxonomy (subject, domain, topics, objectives) |
|
||
| `encoach.resource.tag` | `encoach_resource_tag` | Tags for categorizing resources |
|
||
| `encoach.subject` | `encoach_subject` | Taxonomy subjects (e.g. English, Mathematics) |
|
||
| `encoach.domain` | `encoach_domain` | Taxonomy domains under subjects (e.g. Grammar, Listening) |
|
||
| `encoach.topic` | `encoach_topic` | Taxonomy topics under domains |
|
||
| `encoach.learning.objective` | `encoach_learning_objective` | Learning objectives under topics (Bloom levels) |
|
||
| `op.course` (extended) | `op_course` | OpenEduCat course + `description`, `max_capacity`, `encoach_subject_id`, `topic_ids`, `learning_objective_ids` |
|
||
| `op.student` | `op_student` | OpenEduCat student model |
|
||
| `op.faculty` | `op_faculty` | OpenEduCat teacher/faculty model |
|
||
| `op.batch` | `op_batch` | OpenEduCat batch model |
|
||
|
||
### Institutional models (OpenEduCat + EnCoach)
|
||
|
||
| Model | Table | Description |
|
||
|-------|-------|-------------|
|
||
| `op.academic.year` | `op_academic_year` | Academic year (name unique, `term_structure`) |
|
||
| `op.academic.term` | `op_academic_term` | Term under a year; FK to year is `RESTRICT` — delete-year cascades terms first |
|
||
| `op.department` | `op_department` | Department |
|
||
| `op.admission.register` | `op_admission_register` | Admission register window (`start_date` / `end_date`) |
|
||
| `op.admission` | `op_admission` | Admission — constrained so `application_date ∈ [register.start_date, register.end_date]` |
|
||
| `op.exam.session` | `op_exam_session` | Institutional exam session |
|
||
| `op.result.template` | `op_result_template` | Marksheet result template — `generate_result()` in Odoo 19 (older: `action_generate_marksheet`) produces `op.marksheet.register` rows |
|
||
| `op.marksheet.register` | `op_marksheet_register` | Generated marksheet register (lines + validate workflow) |
|
||
| `op.facility` | `op_facility` | Physical facility (rooms, labs, etc.) |
|
||
| `encoach.asset` | `encoach_asset` | **New lightweight asset model** (replaces missing `op.asset`): `name`, `code`, `facility_id`, `description`, `active` |
|
||
| `op.activity.type` / `op.activity` | `op_activity_type` / `op_activity` | Activity categorization + activities |
|
||
| `op.media` / `op.author` | `op_media` / `op_author` | Library media + authors (M2M) |
|
||
| `encoach.lesson` | `encoach_lesson` | Lesson tied to course + batch |
|
||
| `grading.assignment` / `grading.assignment.type` | `grading_assignment*` | Grading assignments — `assignment_type` and `faculty_id` are required |
|
||
| `encoach.student.leave` | `encoach_student_leave` | Student leave requests with approve workflow |
|
||
| `op.fees.terms` + `.line` | `op_fees_terms*` | Fees terms; `line_ids` must sum to 100% |
|
||
| `op.student.fees.details` | `op_student_fees_details` | Per-student fees; `get_invoice()` requires an income account on product template (stored as JSONB in Odoo 19) |
|
||
|
||
---
|
||
|
||
## 9. Installed EnCoach Modules
|
||
|
||
| Module | Description |
|
||
|--------|-------------|
|
||
| `encoach_adaptive` | Adaptive learning engine |
|
||
| `encoach_ai` | AI generation, search, insights |
|
||
| `encoach_ai_course` | AI course creation |
|
||
| `encoach_api` | REST API auth + base helpers |
|
||
| `encoach_branding` | Entity branding |
|
||
| `encoach_core` | Core models (user, entity, role) |
|
||
| `encoach_course_gen` | Course generation |
|
||
| `encoach_exam_template` | Exam CRUD, sessions, rubrics, assignments |
|
||
| `encoach_lms_api` | LMS: courses, students, teachers, batches, chapters, materials, enrollment, progress |
|
||
| `encoach_quality_gate` | Quality gate checks |
|
||
| `encoach_resources` | Learning resources |
|
||
| `encoach_scoring` | Grading and score release |
|
||
| `encoach_taxonomy` | Subject/topic taxonomy |
|
||
| `encoach_vector` | Vector embeddings |
|
||
|
||
---
|
||
|
||
## 10. Exam Lifecycle Flow
|
||
|
||
```
|
||
1. Admin → /admin/generation page
|
||
├── Select modules (reading, listening, writing, speaking)
|
||
├── Set parameters (difficulty, timer, grading, rubric, entity, etc.)
|
||
└── Click "Generate with AI" → POST /api/exam/<module>/generate
|
||
|
||
2. AI generates content (or fallback templates if OpenAI not configured)
|
||
|
||
3. Admin reviews generated content → Submit
|
||
├── "Submit & skip approval" → POST /api/exam/generation/submit (skip_approval=true)
|
||
├── "Submit for approval" → POST /api/exam/generation/submit (skip_approval=false)
|
||
└── Creates: exam_custom + sections + questions in DB
|
||
|
||
4. Admin assigns exam → /api/exam/custom/<id>/assign or /api/exam-schedules
|
||
|
||
5. Student → /student/my-exams → clicks exam → /student/exam/<id>
|
||
├── GET /api/exam/<id>/session → loads sections, questions, passage_text
|
||
├── POST /api/exam/<id>/autosave → saves answers periodically
|
||
└── POST /api/exam/<id>/submit → auto-scores objective questions
|
||
|
||
6. Student → /student/exam/<id>/results → views score breakdown
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Student Learning Lifecycle Flow
|
||
|
||
```
|
||
1. Admin/Teacher → /teacher/courses → "New Course"
|
||
├── Create course (title, code, description, max_capacity)
|
||
└── Course stored in op.course (extended with description, max_capacity)
|
||
|
||
2. Teacher → /teacher/courses/:courseId/chapters → "Add Chapter"
|
||
├── Create chapters with sequence ordering
|
||
└── Upload materials (pdf, video, article, link) per chapter
|
||
└── POST /api/chapters/<id>/materials (JSON or FormData)
|
||
|
||
3. Admin → /admin/courses → "Enroll Students"
|
||
├── Bulk-enroll students via POST /api/courses/<id>/enroll
|
||
└── Or enroll individually via POST /api/students/<id>/enroll
|
||
|
||
4. Student → /student/dashboard → sees enrolled courses with progress
|
||
├── GET /api/student/my-courses → returns courses + chapter_count + progress %
|
||
└── Cards show "Start Learning" or "Continue Learning"
|
||
|
||
5. Student → /student/courses/:id → course detail with chapter list
|
||
├── Shows chapter list with material counts and lock status
|
||
└── Student clicks a chapter → /student/courses/:courseId/chapters/:chapterId
|
||
|
||
6. Student → chapter view → views materials
|
||
├── POST /api/materials/<id>/view → marks material as viewed
|
||
├── Progress tracked in encoach.chapter.progress
|
||
└── When all materials viewed → chapter auto-completes
|
||
|
||
7. Student → clicks "Mark Chapter Complete"
|
||
├── POST /api/chapters/<id>/progress/complete
|
||
└── Triggers _check_course_completion()
|
||
|
||
8. Course completion check
|
||
├── When all chapters completed → encoach.course.completion updated
|
||
├── progress = 100%, completed_at = now
|
||
└── post_test_available = True → student can take final exam
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Taxonomy ↔ Resource ↔ Course Relationships
|
||
|
||
```
|
||
encoach.subject (e.g. English)
|
||
└── encoach.domain (e.g. Grammar, Listening, Reading)
|
||
└── encoach.topic (e.g. Verb Tenses, Relative Clauses)
|
||
└── encoach.learning.objective (Bloom levels: Remember, Understand, Apply...)
|
||
|
||
encoach.resource ──M2O──→ encoach.subject (subject_id)
|
||
──M2O──→ encoach.domain (domain_id)
|
||
──M2M──→ encoach.topic (topic_ids)
|
||
──M2M──→ encoach.learning.objective (learning_objective_ids)
|
||
|
||
op.course (extended) ──M2O──→ encoach.subject (encoach_subject_id)
|
||
──M2M──→ encoach.topic (topic_ids)
|
||
──M2M──→ encoach.learning.objective (learning_objective_ids)
|
||
|
||
encoach.course.chapter ──M2O──→ encoach.subject (encoach_subject_id)
|
||
──M2M──→ encoach.topic (topic_ids)
|
||
──M2M──→ encoach.learning.objective (learning_objective_ids)
|
||
```
|
||
|
||
### Frontend: `TaxonomyCascade` Component
|
||
|
||
Reusable cascading picker (`frontend/src/components/TaxonomyCascade.tsx`) used in:
|
||
- Admin Resource Manager (upload + edit dialogs)
|
||
- Teacher Library (upload dialog)
|
||
|
||
Provides Subject → Domain → Topics → Learning Objectives selection with auto-fetching and reset-on-parent-change logic.
|
||
|
||
### API endpoints for taxonomy data
|
||
|
||
| Method | Route | Description |
|
||
|--------|-------|-------------|
|
||
| GET | `/api/subjects` | List subjects (paginated, returns `items` array) |
|
||
| GET | `/api/domains?subject_id=N` | List domains filtered by subject |
|
||
| GET | `/api/topics?domain_id=N` | List topics filtered by domain |
|
||
| GET | `/api/learning-objectives?topic_id=N` | List objectives filtered by topic |
|
||
|
||
---
|
||
|
||
## 13. Known Issues & Notes
|
||
|
||
1. **PostgreSQL version**: pgdata is PG 18; must use micromamba `pg_ctl` (`/Users/yamenahmad/micromamba/envs/odoo19/bin/pg_ctl`), NOT conda env PG 17.
|
||
2. **`odoo.conf` requires `db_name`**: Without `db_name = encoach_v2`, all API routes return 404.
|
||
3. **`npm` not in default PATH**: Must add `/Users/yamenahmad/.local/node/bin` to PATH before running frontend.
|
||
4. **OpenAI not configured**: AI generation uses fallback templates (static mock content). Set OpenAI API key in Odoo system parameters to enable real AI.
|
||
5. **Gitea deployment hook**: Frontend v3 server has a `docker-compose.yml` conflict that blocks auto-deploy. SSH and stash/checkout on server to fix.
|
||
6. **Student passwords were reset**: Student demo users (`sarah`, `omar`, `layla`) now use password **`student123`** (not `admin`). Passwords were reset via `psycopg2` + `passlib` script. To reset again:
|
||
```python
|
||
from passlib.context import CryptContext
|
||
ctx = CryptContext(schemes=['pbkdf2_sha512'])
|
||
new_hash = ctx.hash('student123')
|
||
# UPDATE res_users SET password = '<new_hash>' WHERE login = 'sarah@encoach.test';
|
||
```
|
||
7. **`op.course` extended fields**: The `op.course` model (OpenEduCat) was extended via `encoach_lms_api/models/course_ext.py` to add `description` (Text) and `max_capacity` (Integer). If the module is not installed/updated, course creation will fail with `ValueError: Invalid field 'description'`.
|
||
8. **`material_count` computed field**: The `material_count` on `encoach.course.chapter` requires `@api.depends('material_ids')` to recompute when materials are added. If counts show 0 after adding materials, restart Odoo with `-u encoach_lms_api`.
|
||
9. **Taxonomy API response format**: Taxonomy list endpoints (`/api/subjects`, `/api/domains`, `/api/topics`) return paginated responses with data under an `items` key (`{"items": [...], "total": N}`), not a `data` key. The frontend `taxonomy.service.ts` handles both formats.
|
||
10. **Resource taxonomy fields via FormData**: When uploading resources, `topic_ids` and `learning_objective_ids` are sent as comma-separated strings in multipart FormData. The backend `create_resource` handler parses these into proper Many2many commands `[(6, 0, ids)]`.
|
||
11. **Module upgrades for schema changes**: After adding new fields to Odoo models (e.g. `encoach_subject_id` on `op.course`), you must run `./run.sh -u <module_name> --stop-after-init` to apply DB schema changes before the server can use them.
|
||
|
||
---
|
||
|
||
## 14. Frontend Key Pages
|
||
|
||
### Admin Pages
|
||
| Route | Component | Description |
|
||
|-------|-----------|-------------|
|
||
| `/admin/dashboard` | AdminLmsDashboard | Platform overview, AI tips, stats (LMS-focused) |
|
||
| `/admin/platform` | AdminDashboard | Alternative platform dashboard |
|
||
| `/admin/courses` | AdminCourses | Course table with enroll/assign/delete actions |
|
||
| `/admin/students` | AdminStudents | Student management |
|
||
| `/admin/teachers` | AdminTeachers | Teacher management |
|
||
| `/admin/users` | UsersPage | All platform users + roles (Management section) |
|
||
| `/admin/entities` | EntitiesPage | Entity/organization management |
|
||
| `/admin/classrooms` | ClassroomsPage | Batches/classrooms with student management |
|
||
| `/admin/user-roles` | UserRoles | User role assignments (⚠ NOT `/admin/roles`) |
|
||
| `/admin/roles-permissions` | RolesPermissions | Roles & permissions matrix |
|
||
| `/admin/authority-matrix` | AuthorityMatrix | Authority matrix across roles |
|
||
| `/admin/generation` | GenerationPage | AI exam generation |
|
||
| `/admin/examsList` | ExamsListPage | List all exams (⚠ NOT `/admin/exams`) |
|
||
| `/admin/rubrics` | RubricsPage | Manage rubrics |
|
||
| `/admin/assignments` | AssignmentsPage | Manage assignments |
|
||
| `/admin/approval-workflows` | ApprovalWorkflowsPage | Approval workflow config |
|
||
| `/admin/taxonomy` | TaxonomyManager | Manage subjects, domains, topics, objectives (inline editable) |
|
||
| `/admin/resources` | ResourceManager | Upload/edit resources with full taxonomy cascade (Subject→Domain→Topics→Objectives) |
|
||
|
||
### Admin — Institutional Pages
|
||
|
||
Backed by `encoach_lms_api` + OpenEduCat models. All 14 pages verified CREATE / EDIT / DELETE / WORKFLOW in the write-flow sweep (see §17).
|
||
|
||
| Route | Page | Backing API | Key actions |
|
||
|-------|------|-------------|-------------|
|
||
| `/admin/academic-years` | Academic Years | `/api/academic-years`, `/generate-terms` | CREATE, generate-terms, EDIT, DELETE (cascades terms) |
|
||
| `/admin/academic-terms` | Academic Terms | `/api/academic-terms` | CRUD |
|
||
| `/admin/departments` | Departments | `/api/departments` | CRUD |
|
||
| `/admin/admission-registers` | Admission Registers | `/api/admission-registers` | CREATE, confirm, DELETE |
|
||
| `/admin/admissions` | Admissions | `/api/admissions` | CREATE (date-constrained), DELETE |
|
||
| `/admin/inst-exam-sessions` | Institutional Exam Sessions | `/api/inst-exam-sessions` | CREATE, schedule, EDIT, DELETE |
|
||
| `/admin/marksheets` | Marksheets | `/api/result-templates`, `/api/marksheets` | Create template, generate, validate marksheet, DELETE |
|
||
| `/admin/facilities` | Facilities | `/api/facilities`, `/api/assets` | Facility CRUD + nested assets |
|
||
| `/admin/activities` | Activities | `/api/activity-types`, `/api/activities` | Type + activity CRUD |
|
||
| `/admin/library` | Library | `/api/library/media` | Media + authors, detail exposes author links |
|
||
| `/admin/lessons` | Lessons | `/api/lessons` | CRUD; edits preserve `course_id` / `batch_id` |
|
||
| `/admin/gradebook` | Gradebook | `/api/grading-assignments`, `/api/gradebooks`, `/api/gradebook-lines` | Assignment CRUD + gradebook drill |
|
||
| `/admin/student-progress` | Student Progress | `/api/student-progress` | Drill-through detail |
|
||
| `/admin/student-leave` | Student Leave | `/api/student-leaves` | CREATE, approve, DELETE |
|
||
| `/admin/fees` | Fees | `/api/fees-plans`, `/create-invoice`, `/register-payment` | List seeded plans, generate invoice, register payment |
|
||
|
||
### Teacher Pages
|
||
| Route | Component | Description |
|
||
|-------|-----------|-------------|
|
||
| `/teacher/dashboard` | TeacherDashboard | Teacher overview |
|
||
| `/teacher/courses` | TeacherCourses | Course cards with Edit, Chapters & Materials, AI Workbench |
|
||
| `/teacher/courses/new` | CourseBuilder | Create new course |
|
||
| `/teacher/courses/:courseId/chapters` | CourseChapters | Manage chapters and materials for a course |
|
||
| `/teacher/courses/:courseId/chapters/:chapterId` | ChapterDetail | Chapter detail with material list |
|
||
| `/teacher/courses/:courseId/workbench` | AiWorkbench | AI content generation workbench |
|
||
| `/teacher/assignments` | TeacherAssignments | Manage assignments |
|
||
| `/teacher/library` | TeacherLibrary | Upload resources with full taxonomy cascade (Subject→Domain→Topics→Objectives) |
|
||
|
||
### Student Pages
|
||
| Route | Component | Description |
|
||
|-------|-----------|-------------|
|
||
| `/student/dashboard` | StudentDashboard | Enrolled courses, progress, grades, quick actions |
|
||
| `/student/courses` | StudentCourses | Enrolled courses with progress badges |
|
||
| `/student/courses/:id` | StudentCourseDetail | Course chapters, materials, grades |
|
||
| `/student/courses/:courseId/chapters/:chapterId` | StudentChapterView | View materials, mark viewed/complete |
|
||
| `/student/my-exams` | StudentExamsPage | Student's assigned exams |
|
||
| `/student/exam/:id` | ExamSession | Take an exam |
|
||
| `/student/exam/:id/results` | ExamResults | View results |
|
||
| `/student/assignments` | StudentAssignments | Homework assignments |
|
||
|
||
---
|
||
|
||
## 15. Database Connection
|
||
|
||
```
|
||
Host: localhost
|
||
Port: 5432
|
||
Database: encoach_v2
|
||
User: yamenahmad
|
||
Password: (none)
|
||
```
|
||
|
||
Quick access:
|
||
```bash
|
||
PGBIN="/Users/yamenahmad/micromamba/envs/odoo19/bin"
|
||
"$PGBIN/psql" -U yamenahmad -d encoach_v2
|
||
```
|
||
|
||
---
|
||
|
||
## 16. QA Test Report — 2026-04-18
|
||
|
||
A full end-to-end browser test was executed across all three user types (admin, teacher, student), driven by an autonomous browser agent. 32 distinct pages were verified plus an exam session flow.
|
||
|
||
### 16.1 Backend API Health — 32/33 endpoints healthy (pre-fix)
|
||
|
||
All tested with admin JWT unless noted:
|
||
|
||
| Category | Endpoints Tested | Status |
|
||
|----------|-----------------|--------|
|
||
| Auth | `/api/login`, `/api/user` | ✅ |
|
||
| Users & Roles | `/api/users/list`, `/api/users/with-roles`, `/api/roles`, `/api/permissions`, `/api/authority-matrix`, `/api/user-authority-matrix` | ✅ |
|
||
| Entities | `/api/entities`, `/api/entities/<id>/roles` | ✅ |
|
||
| LMS Core | `/api/courses`, `/api/students`, `/api/teachers`, `/api/batches`, `/api/batches/<id>/students` | ✅ |
|
||
| Taxonomy | `/api/subjects`, `/api/domains`, `/api/topics`, `/api/learning-objectives` | ✅ |
|
||
| Resources | `/api/resources`, `/api/resource-tags` | ✅ |
|
||
| Exams | `/api/exam/custom/list`, `/api/exam/templates`, `/api/exam-structures`, `/api/exam-schedules`, `/api/rubrics`, `/api/rubric-groups` | ✅ |
|
||
| Assignments | `/api/assignments` | ✅ |
|
||
| Stats | `/api/stats`, `/api/stats/performance`, `/api/analytics/student`, `/api/adaptive/dashboard` | ✅ |
|
||
| Courseware | `/api/courses/<id>/chapters`, `/api/courses/<id>/completion`, `/api/materials/search` | ✅ |
|
||
| Grading | `/api/grading/queue` | ✅ |
|
||
| Student role | `/api/student/my-courses`, `/api/student/my-exams`, `/api/assignments` | ✅ |
|
||
| Approvals | `/api/approval-workflows`, `/api/approval-requests`, `/api/approval-users` | ✅ (after fix) |
|
||
|
||
### 16.2 Bugs Fixed During This Pass
|
||
|
||
**Bug #1: `/api/approval-workflows` returned 500**
|
||
- **Cause**: `encoach_exam_template/controllers/approval_workflows.py` ran raw SQL against tables (`encoach_approval_workflow`, `encoach_approval_request`) that are only created by the `encoach_approval` module — which lives in `new_project/` and is NOT installed in the active `backend/`.
|
||
- **Fix**: Added a `_table_exists(cr, name)` helper using `to_regclass()`. Both `list_workflows` and `list_requests` now return `{items: [], total: 0}` gracefully when the table is absent, matching the frontend's empty-state expectation.
|
||
|
||
**Bug #2: `DELETE /api/courses/<id>` returned opaque 500 for courses with enrollments**
|
||
- **Cause**: `op.course` has a `RESTRICT` FK from `op_student_course`. Postgres raised a raw violation with no user-friendly handling.
|
||
- **Fix**: `delete_course` in `encoach_lms_api/controllers/lms_core.py` now (a) detects enrollments first and returns a 409 with a clear message, (b) accepts `?force=true` to cascade-delete enrollments and detach linked batches before removing the course.
|
||
|
||
### 16.3 Admin Workflows — All Pages Pass ✅
|
||
|
||
| Page | Route | Result |
|
||
|------|-------|--------|
|
||
| Dashboard | `/admin/dashboard` | Stats cards, AI tips, platform insights render |
|
||
| Users | `/admin/users` | 7 users shown with roles, filter tabs work |
|
||
| Entities | `/admin/entities` | Demo Academy entity, 6 users / 5 roles |
|
||
| Classrooms | `/admin/classrooms` | IELTS 2026 Spring (3 students), student dialog works |
|
||
| User Roles | `/admin/user-roles` | 7 users, 5 available roles, assignments rendered |
|
||
| Roles & Permissions | `/admin/roles-permissions` | 5 roles × 115 permissions × 22 categories |
|
||
| Authority Matrix | `/admin/authority-matrix` | 257 granted cells, 45 % coverage, CSV export present |
|
||
| Courses | `/admin/courses` | 3 courses, enroll-students dialog with BOTH "Individual" and "By Classroom" tabs verified |
|
||
| Taxonomy | `/admin/taxonomy` | 3 subjects (English, Math, IT) with full tree |
|
||
| Resources | `/admin/resources` | 15 items (6 library + 9 course materials), filters & cascade pickers work |
|
||
| Exams List | `/admin/examsList` | 16 custom exams, search filters correctly |
|
||
| Generation | `/admin/generation` | Loads; Official/Practice tabs work |
|
||
| Rubrics | `/admin/rubrics` | 3 rubrics + groups tab |
|
||
| Assignments | `/admin/assignments` | 2 assignments, filter tabs work |
|
||
| Approval Workflows | `/admin/approval-workflows` | Empty-state OK, Create dialog opens |
|
||
|
||
### 16.4 Teacher Workflows — All Pages Pass ✅
|
||
|
||
Logged in as `khalid@encoach.test` (Dr. Khalid). Tested 11 pages; all work:
|
||
|
||
- `/teacher/dashboard` — 3 Active Courses, 55 Total Students, 0 Pending Grading, 82 % Avg Pass Rate
|
||
- `/teacher/courses` — 3 course cards with Edit / Chapters / AI Workbench buttons
|
||
- `/teacher/students` — 3 students with batch info, attendance, AI risk indicators
|
||
- `/teacher/library` — 15 resources, upload dialog's taxonomy cascade confirmed working
|
||
- `/teacher/assignments` — empty state handled gracefully
|
||
- `/teacher/courses/3/chapters` — empty state + "Add Chapter" dialog opens with Name/Description/Start Date/Unlock Mode
|
||
- `/teacher/attendance` — batch selector + trend chart + AI warning banners
|
||
- `/teacher/timetable` — weekly grid 08:00–17:00
|
||
- `/teacher/discussions` / `/teacher/announcements` / `/teacher/profile` — all load cleanly
|
||
|
||
### 16.5 Student Workflows — All Pages Pass ✅
|
||
|
||
Logged in as `sarah@encoach.test` (password `student123`). Tested 16 pages + exam session; all work:
|
||
|
||
- `/student/dashboard` — 67 % overall progress, 3 courses, 4 upcoming exams popup
|
||
- `/student/courses` — 3 cards with progress (100 %, 100 %, 0 %)
|
||
- `/student/courses/1` — course detail with "Chapters (1) / Grades (0) / About" tabs + "Take Post-Test"
|
||
- `/student/courses/1/chapters/1` — 3/3 materials completed (IELTS Listening video, Reading article, Grammar docs)
|
||
- `/student/assignments` / `/student/grades` / `/student/attendance` / `/student/timetable` — all load with graceful empty or populated states
|
||
- `/student/profile` — editable name, email, password change
|
||
- `/student/discussions` / `/student/announcements` / `/student/messages` / `/student/journey` — empty states clean
|
||
- `/student/subjects` — 3 subjects with "Start Diagnostic" CTAs
|
||
- `/student/subject-registration` — registration table renders
|
||
|
||
**Exam flow** — `/student/exam/2/session` loaded "IELTS Academic Mock - Spring 2026 Full Test" with 170-minute timer, Listening section, 7 questions, MCQ with 4 radio options, Previous / Flag / Next navigation. Clean and functional.
|
||
|
||
### 16.6 Demo Data State (Post-Cleanup)
|
||
|
||
| Entity | Count | Notes |
|
||
|--------|-------|-------|
|
||
| Users | 7 | 2 admins, 2 teachers, 3 students |
|
||
| Courses | 3 | IELTS Preparation B2, Python Programming Fundamentals, IELTS Listening & Speaking Intensive (junk "tttttt" removed, "test" renamed) |
|
||
| Batches | 1 | IELTS 2026 Spring (3 students) |
|
||
| Exams (custom) | 16 | published + draft mix |
|
||
| Resources | 6 | library resources; +9 course materials |
|
||
| Taxonomy subjects | 3 | English (6d/37t), Mathematics (6d/15t), IT (6d/17t) — junk "math" subject removed |
|
||
|
||
### 16.7 Route Naming Gotchas
|
||
|
||
Two frontend routes differ from the pattern a developer might guess:
|
||
|
||
| Expected | Actual |
|
||
|----------|--------|
|
||
| `/admin/exams` | `/admin/examsList` |
|
||
| `/admin/roles` | `/admin/user-roles` |
|
||
|
||
The `Admin Pages` table in §14 has been updated with the full correct route list.
|
||
|
||
### 16.8 Minor Observations (Non-Blocking)
|
||
|
||
- React Router v7 future flag warnings in console on every page (`v7_startTransition`, `v7_relativeSplatPath`) — cosmetic only, ignore until v7 migration.
|
||
- forwardRef warning in Badge component — dev-only, not a runtime error.
|
||
- `/api/scores` by itself returns 404, but its sub-endpoints (`/api/scores/release` etc.) are valid.
|
||
- Session occasionally expired during long admin test runs, requiring re-login. Token TTL may warrant increasing for QA flows.
|
||
|
||
---
|
||
|
||
## 17. Institutional Admin Pages — Write-Flow QA — 2026-04-18
|
||
|
||
Comprehensive API-level CREATE / EDIT / DELETE / WORKFLOW sweep across all 14 institutional admin pages (see §14 "Admin — Institutional Pages"). Driven by `test_write_flows.py` against real Odoo data seeded by `seed_institutional.py`.
|
||
|
||
**Final result: `SUMMARY PASS=44 FAIL=0 TOTAL=44`** ✅
|
||
|
||
### 17.1 Scenarios covered
|
||
|
||
| Page | Actions verified |
|
||
|------|------------------|
|
||
| Academic Years | CREATE, generate-terms (3), EDIT, DELETE (cascades terms) |
|
||
| Academic Terms | EDIT, DELETE |
|
||
| Departments | CREATE, EDIT, DELETE |
|
||
| Admission Registers | CREATE, confirm, DELETE |
|
||
| Admissions | CREATE (date-constrained to register window), DELETE |
|
||
| Inst Exam Sessions | CREATE, schedule, EDIT, DELETE |
|
||
| Marksheets | CREATE template, generate-marksheets, validate marksheet, DELETE template (cascades registers) |
|
||
| Facilities | CREATE, EDIT, DELETE |
|
||
| Assets | CREATE, DELETE (backed by new `encoach.asset` model) |
|
||
| Activities | CREATE type, CREATE activity, DELETE |
|
||
| Library | CREATE media + authors, VERIFY author link, DELETE |
|
||
| Lessons | CREATE, EDIT (preserves `course_id` + `batch_id`), DELETE |
|
||
| Gradebook | CREATE grading-assignment, DELETE, drill lines |
|
||
| Student Progress | DRILL detail |
|
||
| Student Leaves | CREATE, approve, DELETE |
|
||
| Fees | LIST seeded, create-invoice ✅ |
|
||
|
||
### 17.2 Backend fixes applied
|
||
|
||
**1. `encoach.asset` — new lightweight model**
|
||
`op.asset` does not exist in the installed OpenEduCat set. Added `encoach_lms_api/models/asset.py` with `name`, `code`, `facility_id`, `description`, `active`; registered in `__init__.py`; CRUD access granted via `security/ir.model.access.csv`. `facilities.py` now uses `_asset_model()` → `env['encoach.asset']`.
|
||
|
||
**2. Missing DELETE routes added**
|
||
- `DELETE /api/admission-registers/<rid>`
|
||
- `DELETE /api/admissions/<aid>`
|
||
- `DELETE /api/result-templates/<tid>` (cascades `op.marksheet.register` rows created by `generate_result`)
|
||
- `DELETE /api/student-leaves/<lid>`
|
||
- `DELETE /api/academic-years/<yid>` now pre-unlinks `academic_term_ids` to avoid the `op_academic_term.academic_year_id` FK RESTRICT violation.
|
||
|
||
**3. Missing detail (GET-one) endpoints added**
|
||
- `GET /api/lessons/<lid>` — exposes full lesson incl. `course_id`, `batch_id` so EDIT can verify those are preserved.
|
||
- `GET /api/library/media/<mid>` — returns `author_ids` + `author_names` (the list endpoint only returns a comma-separated `author` string).
|
||
|
||
**4. Admissions CREATE — defaults + validation**
|
||
`create_admission` now defaults `application_date = fields.Date.today()`, builds `name` from `first_name + last_name`, and accepts `nationality_id` or `nationality`. The Odoo constraint `@api.constrains(...)` requires `application_date ∈ [register.start_date, register.end_date]` — the test payload uses `2028-06-15` to sit inside the seeded register window. Added companion `PATCH/PUT /api/admissions/<aid>` for edits.
|
||
|
||
**5. Grading assignments — required fields resolved**
|
||
`create_grading_assignment` no longer sends `user_id` (invalid on `grading.assignment` in this OpenEduCat version). Auto-resolves the required fields:
|
||
- `assignment_type`: find-or-create `grading.assignment.type` with code `DEFAULT`
|
||
- `faculty_id`: first available `op.faculty`
|
||
- `issued_date`: defaults to `fields.Datetime.now()`
|
||
|
||
**6. Marksheets generate — Odoo 19 compatibility**
|
||
Route aliased as `/api/result-templates/<tid>/generate` **and** `/generate-marksheets`. Controller tries `rec.generate_result()` (OpenEduCat 19) then falls back to `rec.action_generate_marksheet()` (older versions).
|
||
|
||
**7. Fees create-invoice — Odoo 19 JSONB properties**
|
||
OpenEduCat's `op.student.fees.details.get_invoice()` raised `UserError: There is no income account defined for this product`. Root cause: **Odoo 19 removed `ir_property` and stores company-dependent fields (like `property_account_income_id`) as JSONB columns on the model itself** — e.g. `product_template.property_account_income_id` is now `jsonb` keyed by `company_id`.
|
||
|
||
Fix: `create_invoice` auto-wires a default income account before invoking `get_invoice()`:
|
||
|
||
```241:260:backend/custom_addons/encoach_lms_api/controllers/fees.py
|
||
if not (fp or {}).get('income'):
|
||
AA = request.env['account.account'].sudo()
|
||
# Odoo 19: account.account has a M2M `company_ids`.
|
||
try:
|
||
income_acc = AA.search([
|
||
('account_type', '=', 'income'),
|
||
('company_ids', 'in', request.env.company.id),
|
||
], limit=1)
|
||
except Exception:
|
||
income_acc = AA.browse()
|
||
if not income_acc:
|
||
income_acc = AA.search([('account_type', '=', 'income')], limit=1)
|
||
if income_acc:
|
||
try:
|
||
prod_tmpl.with_company(request.env.company).write({
|
||
'property_account_income_id': income_acc.id,
|
||
})
|
||
except Exception:
|
||
_logger.exception('auto-wire income account')
|
||
```
|
||
|
||
The `seed_institutional.py` fee-product block now performs the same wiring at seed time, so fresh databases don't hit this error.
|
||
|
||
### 17.3 Seeding
|
||
|
||
`seed_institutional.py` populates the full institutional fixture set:
|
||
|
||
- 1 academic year + auto-generated terms
|
||
- 1 department
|
||
- 1 admission register + 1 admission
|
||
- 1 exam session + 1 result template → marksheets
|
||
- Facilities, activities, library media, lessons, gradebooks, student leaves
|
||
- **Fees**: 3 `op.fees.terms` (Full, 2-inst 50/50, 3-inst 34/33/33 summing to 100%), 1 `product.product` (`IELTS Preparation B2 Fees`) with income account wired, 3 `op.student.fees.details` records
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd /Users/yamenahmad/projects2026/odoo/odoo19
|
||
./run.sh --shell <<< "exec(open('seed_institutional.py').read())"
|
||
```
|
||
|
||
### 17.4 Running the write-flow test
|
||
|
||
```bash
|
||
cd /Users/yamenahmad/projects2026/odoo/odoo19
|
||
python3 test_write_flows.py
|
||
```
|
||
|
||
The script is **idempotent** — it uses timestamped names for academic-year/register payloads so repeated runs don't collide on Odoo's unique constraints. Expected output: `SUMMARY PASS=44 FAIL=0 TOTAL=44`.
|
||
|
||
### 17.5 Operational notes for Odoo 19 institutional data
|
||
|
||
- `product_template.property_account_income_id` is JSONB keyed by `company_id` — write to it with `.with_company(company).write({...})`, **not** via the removed `ir_property` table.
|
||
- `op.admission` has a constraint that `application_date` must fall inside the parent `op.admission.register`'s `[start_date, end_date]` — front-end forms should default to `register.start_date` to avoid validation errors.
|
||
- `op.result.template.generate_result()` creates `op.marksheet.register` rows; deleting a template without first unlinking those rows will fail. The DELETE endpoint handles this internally.
|
||
- `op.academic.year` → `op.academic.term` FK is `RESTRICT`; always unlink terms before the year.
|
||
- `op.fees.terms.line_ids` must sum to 100% (percentages). Seeded terms conform.
|
||
|
||
|
||
## §18 Support Admin Pages — Backend + Write-Flow QA — 2026-04-18
|
||
|
||
### 18.1 Scope
|
||
|
||
The **Support** group in the admin sidebar is 3 pages:
|
||
|
||
| Admin URL | Component | Before | After (this session) |
|
||
|-------------------------------|---------------------------|------------------------------------------------|----------------------|
|
||
| `/admin/tickets` | `TicketsPage` | Called `/api/tickets` which **did not exist** (404 HTML). | Full backend — `encoach.ticket` model + `/api/tickets` CRUD + `assignedToUser`. |
|
||
| `/admin/payment-record` | `PaymentRecordPage` | Hard-coded mock arrays. | Live data from `op.student.fees.details` + `account.move` + stubbed `/api/paymob-orders`. |
|
||
| `/admin/settings-platform` | `SettingsPage` | Hard-coded mock arrays. | Codes tab → `encoach.code`, Packages tab → `ir.config_parameter`, Grading tab → `ir.config_parameter`. |
|
||
|
||
### 18.2 New backend artefacts
|
||
|
||
**Model** — `backend/custom_addons/encoach_lms_api/models/ticket.py`:
|
||
|
||
```python
|
||
class EncoachTicket(models.Model):
|
||
_name = 'encoach.ticket'
|
||
_inherit = ['mail.thread']
|
||
subject, description, type, status, source, page_context,
|
||
reporter_id, assignee_id, entity_id, resolved_at
|
||
```
|
||
|
||
Registered in `models/__init__.py` and `security/ir.model.access.csv` (full CRUD for `base.group_user`).
|
||
|
||
**Controllers** — added three new files, all registered in `controllers/__init__.py`:
|
||
|
||
| File | Routes |
|
||
|-------------------------------------|--------|
|
||
| `controllers/tickets.py` | `GET/POST /api/tickets`, `GET/PATCH/DELETE /api/tickets/<id>`, `GET /api/tickets/assignedToUser` |
|
||
| `controllers/payments.py` | `GET /api/payment-records` (derived from `op.student.fees.details`), `GET /api/payment-records/invoices` (from `account.move` out_invoice), `GET /api/paymob-orders` (stub, returns `{data:[], message:"Paymob integration not configured."}`) |
|
||
| `controllers/platform_settings.py` | `GET/POST/DELETE /api/codes`, `POST /api/codes/generate`, `GET/POST/PATCH/DELETE /api/packages`, `GET/PATCH /api/grading-config` |
|
||
|
||
Packages and grading config are persisted in `ir.config_parameter` under the keys `encoach.platform.packages` and `encoach.platform.grading` — no new model table needed.
|
||
|
||
### 18.3 Frontend changes
|
||
|
||
| File | Change |
|
||
|--------------------------------------------------|--------|
|
||
| `frontend/src/services/payments.service.ts` | **NEW** — typed client for `/api/payment-records` + `/api/paymob-orders`. |
|
||
| `frontend/src/services/platformSettings.service.ts` | **NEW** — typed client for codes, packages, grading-config. |
|
||
| `frontend/src/pages/PaymentRecordPage.tsx` | Rewritten: 4 KPI cards + live Payments table + Paymob empty-state + CSV export. Removed all mock arrays. |
|
||
| `frontend/src/pages/SettingsPage.tsx` | Rewritten: Codes tab with real Generate Single / Generate Batch and delete; Packages tab with Add Package dialog + delete; Grading tab with Save + client/server validation. |
|
||
| `frontend/src/pages/TicketsPage.tsx` | Small fix: accept both `data.items` and `data.data` from the pagination envelope. |
|
||
|
||
### 18.4 Seeding
|
||
|
||
`seed_institutional.py` now also seeds:
|
||
|
||
- **4 tickets** covering all 4 statuses (`open`, `in_progress`, `resolved`) and all main types (`bug`, `feature`, `support`, `feedback`), with realistic subjects / descriptions / `page_context`.
|
||
- **4 registration codes** (2 individual student, 2 corporate batch) — one already marked as used to exercise the badge variant.
|
||
|
||
### 18.5 Write-flow test — 29/29 PASS
|
||
|
||
`test_support_flows.py` exercises:
|
||
|
||
| Page | Actions verified |
|
||
|----------------------|------------------|
|
||
| Tickets | LIST seeded, filter by status + type, text search, CREATE, GET detail, EDIT → in_progress, EDIT → resolved (auto-stamps `resolved_at`), ASSIGN, `assignedToUser` listing, DELETE |
|
||
| Codes (Settings) | LIST seeded, generate individual ×3, filter `used=false`, DELETE, generate corporate ×2 |
|
||
| Packages (Settings) | LIST defaults, CREATE, EDIT price+discount, DELETE, persistence roundtrip |
|
||
| Grading (Settings) | GET, set new scale, server-side validation (rejects `min >= max`), restore IELTS default |
|
||
| Payment Record | LIST + totals, filter `paid=false`, invoices list (`account.move`), Paymob stub returns empty |
|
||
|
||
Run with:
|
||
|
||
```bash
|
||
cd /Users/yamenahmad/projects2026/odoo/odoo19
|
||
python3 test_support_flows.py
|
||
```
|
||
|
||
Latest output: `SUMMARY PASS=29 FAIL=0 TOTAL=29`.
|
||
|
||
### 18.6 Browser verification
|
||
|
||
All 3 pages were opened in a real browser (localhost:8080) after login as `admin` / `admin` and visually verified:
|
||
|
||
- **Tickets** — 4 seeded rows visible, reporter = "Administrator", Create Ticket dialog submits and a new row appears immediately.
|
||
- **Payment Record** — 4 KPI cards (`Total records`, `Paid`, `Unpaid`, `Total amount`), 3 payment rows for the seeded students, Paymob tab shows the empty-state message.
|
||
- **Settings** — Codes tab lists all seeded + generated codes with `Generate Single` working and a success toast; Packages tab shows the 3 default cards; Grading tab shows 0..9 step 0.5 and `Save` returns a success toast.
|
||
|
||
No critical console errors — only React Router v7 future-flag warnings.
|
||
|
||
### 18.7 Operational notes
|
||
|
||
- The `encoach.ticket` model inherits `mail.thread` so OCA-style change tracking works out of the box. Adding followers / comments will work without further wiring.
|
||
- The `/api/payment-records` endpoint is **derived** (not stored) — it reflects whatever is in `op.student.fees.details`. Creating an invoice from the Fees page will flip the `paid` badge automatically on the Payment Record page once the invoice is paid.
|
||
- `/api/paymob-orders` is a deliberate empty stub. When real Paymob integration is wired, replace the stub body in `controllers/payments.py::list_paymob_orders` — no UI change required; the Payment Record page will light up automatically.
|
||
- Packages and grading config live in `ir.config_parameter`. To reset to defaults, delete the two parameters `encoach.platform.packages` and `encoach.platform.grading`.
|
||
|
||
|
||
## §19 Training Admin Pages — Backend + Write-Flow QA — 2026-04-18
|
||
|
||
### 19.1 Scope
|
||
|
||
The **Training** group in the admin sidebar is 2 pages:
|
||
|
||
| Admin URL | Component | Before | After (this session) |
|
||
|---------------------------------|--------------------|------------------------------------------|----------------------|
|
||
| `/admin/training/vocabulary` | `VocabularyPage` | Hard-coded `vocabItems` array (no API). | Full backend: CRUD + search + level filter + per-user progress tracking. |
|
||
| `/admin/training/grammar` | `GrammarPage` | Hard-coded `grammarItems` array (no API).| Full backend: CRUD + search + level filter + per-user progress tracking. |
|
||
|
||
### 19.2 New backend artefacts
|
||
|
||
**Models** — `backend/custom_addons/encoach_lms_api/models/training.py`:
|
||
|
||
| Model | Key fields |
|
||
|--------------------------------|------------|
|
||
| `encoach.vocab.item` | `word` (unique), `meaning`, `example_sentence`, `level` (CEFR A1–C2), `part_of_speech`, `category`, `active`, computed `learners_count` + `completion_count` |
|
||
| `encoach.vocab.progress` | `user_id`, `vocab_id`, `completed`, `mastery` (learning/familiar/mastered), `last_reviewed`, `review_count` — unique per (user, vocab) |
|
||
| `encoach.grammar.rule` | `name` (unique), `description`, `example`, `level`, `category`, `active`, computed stats |
|
||
| `encoach.grammar.progress` | `user_id`, `rule_id`, `completed`, `last_reviewed`, `review_count` — unique per (user, rule) |
|
||
|
||
All four models are registered in `models/__init__.py` and `security/ir.model.access.csv` (full CRUD for `base.group_user`). Uniqueness is enforced via `@api.constrains` instead of `_sql_constraints` — Odoo 19 deprecated the latter (see operational notes below).
|
||
|
||
**Controller** — `backend/custom_addons/encoach_lms_api/controllers/training.py`:
|
||
|
||
| Method / Path | Verb | Purpose |
|
||
|------------------------------------------------------------|----------|---------|
|
||
| `/api/training/vocabulary` | `GET` | List with filters: `level`, `category`, `active`, `search`, pagination. Returns `items`, `total`, and a `summary` block (`total`, `completed`, `remaining`, `completion_rate`) relative to the authenticated user. |
|
||
| `/api/training/vocabulary` | `POST` | Create (`word` + `meaning` required, unique `word`). |
|
||
| `/api/training/vocabulary/<id>` | `GET` | Fetch a single item with the caller's progress merged in. |
|
||
| `/api/training/vocabulary/<id>` | `PATCH` | Update any mutable field. |
|
||
| `/api/training/vocabulary/<id>` | `DELETE` | Delete (cascades progress). |
|
||
| `/api/training/vocabulary/<id>/progress` | `POST` | Mark completed / set `mastery` for the authenticated user (creates the progress row if absent, increments `review_count`). |
|
||
| `/api/training/grammar` (+ same suffixes) | — | Analogous five endpoints for grammar rules plus `/progress`. |
|
||
|
||
`ValidationError` and `UserError` raised from the model layer now surface as **HTTP 400** (not 500), so the frontend can show user-friendly toasts.
|
||
|
||
### 19.3 Frontend changes
|
||
|
||
| File | Change |
|
||
|--------------------------------------------------|--------|
|
||
| `frontend/src/services/training.service.ts` | **NEW** — typed client for both vocabulary and grammar (list, CRUD, progress). |
|
||
| `frontend/src/pages/VocabularyPage.tsx` | Rewritten. Now: progress summary card with live % complete, search + level filter, checkable list rows, Add Word dialog with CEFR + part-of-speech, delete button per row. AI panel text is dynamic based on `summary.remaining`. |
|
||
| `frontend/src/pages/GrammarPage.tsx` | Rewritten. Same pattern as vocabulary but tailored to grammar (rule name, description, example, category). |
|
||
|
||
### 19.4 Seeding
|
||
|
||
`seed_institutional.py` now seeds:
|
||
|
||
- **10 vocabulary items** spanning B1–C1, with realistic meanings and example sentences (Ubiquitous, Pragmatic, Eloquent, Meticulous, Ambiguous, Coherent, Versatile, Concise, Collaborate, Rationale).
|
||
- **8 grammar rules** spanning A2–C1 (Conditionals Type 2/3, Passive Voice, Relative Clauses, Subject-Verb Agreement, Modal Speculation, Reported Speech, Articles, Present Perfect vs Past Simple).
|
||
- **6 progress rows** for the admin user (3 vocab mastered, 3 grammar completed) so the UI always shows non-zero state on a freshly seeded DB.
|
||
|
||
Run with:
|
||
|
||
```bash
|
||
.conda-envs/odoo19/bin/python odoo/odoo-bin shell -c odoo.conf --no-http <<'EOF'
|
||
exec(open('seed_institutional.py').read())
|
||
EOF
|
||
```
|
||
|
||
The seeding block is **idempotent** — re-running it never creates duplicates (each row checks by unique key first).
|
||
|
||
### 19.5 Write-flow test — 26/26 PASS
|
||
|
||
`test_training_flows.py` exercises:
|
||
|
||
| Page | Actions verified |
|
||
|-------------------|------------------|
|
||
| Vocabulary | LIST seeded, filter by `level=B2`, search `eloqu`, CREATE, **duplicate-word rejection**, **missing-meaning rejection**, GET detail, EDIT level+meaning, PROGRESS mark completed, **summary recomputation** after completion, PROGRESS un-mark, DELETE, GET-after-DELETE → 404 |
|
||
| Grammar | LIST seeded, filter by `level=B1`, search `passive`, CREATE, missing-description rejection, GET detail, EDIT level+category, PROGRESS mark / un-mark, DELETE, GET-after-DELETE → 404 |
|
||
| Pagination envelope | `size=3 page=1` (vocab), `size=3 page=2` (grammar) — confirms `page` + `size` are echoed in the response |
|
||
|
||
Run with:
|
||
|
||
```bash
|
||
cd /Users/yamenahmad/projects2026/odoo/odoo19
|
||
python3 test_training_flows.py
|
||
```
|
||
|
||
Latest output: `SUMMARY PASS=26 FAIL=0 TOTAL=26`.
|
||
|
||
### 19.6 Browser verification
|
||
|
||
Both admin pages were opened in a real browser (localhost:8080) after login as `admin` / `admin`:
|
||
|
||
- **Vocabulary** — progress card shows live `X/N completed (Y%)` with a progress bar; clicking the circle next to a row marks it complete and the summary instantly recomputes; search `"elo"` narrows the list to just *Eloquent*; the *Add Word* dialog creates a new row + shows a toast.
|
||
- **Grammar** — all 8 seeded rules visible (A2–C1), progress tracking works identically; *Add Rule* dialog creates a new row.
|
||
|
||
No functional errors in the console — only React Router v7 future-flag warnings.
|
||
|
||
### 19.7 Operational notes for Odoo 19 training data
|
||
|
||
- **`_sql_constraints` is deprecated in Odoo 19** (the registry emits `Model attribute '_sql_constraints' is no longer supported`). Uniqueness and range checks in these models are therefore implemented with `@api.constrains`, which continues to work on both Odoo 18 and 19.
|
||
- Because both progress models have `ondelete='cascade'` back to the library item, deleting a vocabulary/grammar row automatically cleans up every learner's progress row — no orphan rows.
|
||
- Progress rows are keyed to `res.users.id`, not `op.student`. Any authenticated API user (admin, teacher, student) sees only their own completions in the `summary` block, while `learners_count` / `completion_count` on the library item itself aggregate across all users.
|
||
- The `/api/training/<kind>/<id>/progress` endpoint is **upsert**-like: the first call creates the progress row, subsequent calls update it and bump `review_count`. This lets the frontend treat it as "toggle" without worrying about a missing row.
|
||
|