[CLAUDE] Docs: chốt Session 19 — PE Section 5 V2 dynamic + Mig 26
Chunk D Docs cho Session 19 (PE Section 5 dynamic theo ApprovalWorkflowLevel): - docs/STATUS.md — Recently Done row Session 19 chi tiết + header narrative 25→26 mig + 58→59 tables + 5 spec Q&A + 4 chunk per-commit - docs/HANDOFF.md — TL;DR Session 19 đầy đủ (polish 3 button + Chunk A/B/C summary + Stats Δ) + 7 cảnh báo Session 20+. Giữ TL;DR S18 nguyên văn theo §6.5 (KHÔNG cắt narrative) - docs/changelog/migration-todos.md — Phase 9 Session 19 done block với 5 commit + Stats final + Defer Session 20+ (8 task pending) - docs/changelog/sessions/2026-05-09-0400-pe-section-5-v2-dynamic-mig26.md — Session log mới, full Q&A + Chunks + Bug fix + Stats cumulative - CLAUDE.md (root) — count 25→26 mig + 58→59 tables + Mig 26 description KHÔNG đụng (per §6.5 không cố sửa khi không cần): - rules.md / architecture.md / PROJECT-MAP.md / workflow-contract.md / forms-spec.md / database-guide.md - Skills (6) — drift defer cron audit 2026-06-01 - schema-diagram.md §16 PE Level Opinions V2 — defer cùng §17-21 cron audit 2026-06-01 Path filter docs-only → CI sẽ skip deploy (gotcha #41).
This commit is contained in:
@ -157,6 +157,33 @@ Session log: `2026-04-28-chot-session-4-budget.md`.
|
||||
|
||||
## 📝 Phase 9 — UAT + Ops + carry over (Session 6+ active)
|
||||
|
||||
### ✅ Session 19 done (2026-05-09) — PE Section 5 V2 dynamic theo ApprovalWorkflowLevel + Mig 26 (4 commit `873e7a1` → Chunk D Docs)
|
||||
|
||||
User UAT live tiếp Session 18. 1 polish nhỏ + 1 feature lớn (Section 5 dynamic). Spec chốt 5 câu Q&A trước code (Q1=1B sync auto / Q2=2A+Admin / Q3=V2 hết / Q4=4C+placeholder / Q5=5A grid-cols-2).
|
||||
|
||||
- [x] **Polish 3 button (`873e7a1`) Hành động Workflow Panel** — rút gọn label "✓ Duyệt / ← Trả lại / ✗ Từ chối" (bỏ "→ Chờ X" / "(về Drafter sửa)" / "Hủy /") + 3 màu phân biệt (emerald/amber/red) + font-medium → font-bold. Phase đích vẫn hiện qua tooltip title hover. Mirror fe-admin + fe-user.
|
||||
|
||||
- [x] **Chunk A (`77a3058`) Domain + Mig 26 + EF** — Entity `PurchaseEvaluationLevelOpinion : AuditableEntity` (PEId+LevelId UNIQUE composite, Comment nvarchar(2000), SignedAt datetime2, SignedByUserId Guid, SignedByFullName nvarchar(200) denorm). EF FK Cascade Pe + Restrict Level. **Migration 26** `AddPeLevelOpinionsForV2` (1 CREATE TABLE + 2 FK + 2 index — UNIQUE composite + IX LevelId). 3-file rule. Apply LocalDB SolutionErp_Dev OK (Mig 25 + 26 catchup).
|
||||
|
||||
- [x] **Chunk B (`90baa8e`) Service V2 hook + DTO + GET include** — Service `ApproveV2Async` sau line log approval → UPSERT row LevelOpinion cho Cấp hiện tại (match level theo ApproverUserId == actorUserId, fallback first khi Admin override). Reject KHÔNG sync. Comment empty → "(duyệt — không ý kiến)" placeholder. Helper `ResolveActorFullNameAsync` denorm SignedByFullName. DTO `PurchaseEvaluationLevelOpinionDto` 15 fields. GET handler Include LevelOpinions + helper `BuildLevelOpinionsAsync` JOIN Steps/Levels + Departments + Users → denorm DTO list. Empty cho V1 / V2 chưa có cấp duyệt.
|
||||
|
||||
- [x] **Chunk C (`6e913b3`) FE Section 5 V2 dynamic mirror 2 app** — Type `PeLevelOpinion` + `PeDetailBundle.levelOpinions[]`. Section 5 conditional: `evaluation.approvalWorkflowId` set → `<LevelOpinionsSectionV2/>` (dynamic), else `<DepartmentOpinionsSection readOnly/>` (V1 legacy fallback Mig 15). `LevelOpinionsSectionV2`: forEach Step (header "Bước N — Phòng X" badge emerald + hint số người duyệt) → grid-cols-2 cho `step.levels.flatMap(level => level.approvers.map(approver => <LevelOpinionBox/>))`. `LevelOpinionBox` read-only: title "Cấp N — <ApproverFullName>" + badge amber "⚠ Admin <name> duyệt thay" khi override + badge emerald "✓ Đã duyệt" + empty "— chưa duyệt" + footer signedAt. Mirror fe-admin + fe-user (rule §3.9).
|
||||
|
||||
- [x] **Chunk D Docs (current)** — STATUS Recently Done top + header narrative · HANDOFF TL;DR Session 19 + 7 cảnh báo Session 20+ (giữ S18 nguyên văn theo §6.5) · CLAUDE.md (root) count 25→26 mig + 58→59 tables + Mig 26 description block · migration-todos Phase 9 Session 19 done section + Defer Session 20+ checklist · Session log mới `2026-05-09-0400-pe-section-5-v2-dynamic-mig26.md`. KHÔNG đụng rules/architecture/PROJECT-MAP/workflow-contract/forms-spec/database-guide/schema-diagram (defer cron audit 2026-06-01) — per §6.5 không cố sửa khi không cần.
|
||||
|
||||
**Stats final Session 19:** 26 mig (+1), 59 DB tables (+1), ~141 endpoints (no new — UPSERT auto qua Service hook không endpoint riêng vì Q1=1B), 33 FE pages, **81 test pass** (no change — feature mới UAT defer test §7), 44 gotcha (no new). Memory entries 14 (no new).
|
||||
|
||||
**Defer Session 20+:**
|
||||
|
||||
- [ ] **Test V2 Service wire mới** (Chunk B Service hook) — defer khi UAT user confirm + có sample data Production. Domain test ApproveV2 + UPSERT opinion match logic + Admin override match firstLevel + comment empty placeholder.
|
||||
- [ ] **Drop Mig 15 cho V2 phiếu (cleanup sau UAT confirm)** — sau khi không còn phiếu V2 dùng `PurchaseEvaluationDepartmentOpinions`. Mig 27 cleanup drop bảng + entity. Hoặc giữ cả 2 backward compat.
|
||||
- [ ] **Migrate phiếu V1 cũ sang V2 (data migration)** — admin tool chuyển ApprovalWorkflowId. Hiện chưa làm (Q3 user nói chuyển V2 hết = phiếu MỚI dùng V2, V1 cũ giữ legacy).
|
||||
- [ ] **Contract V2 wire (Mig 27/28)** — mirror PE pattern: Contract.ApprovalWorkflowId + ContractLevelOpinions Mig 28 + Service ApproveV2Async + ContractDetailContent Section 5 V2. Audit-reuse pattern.
|
||||
- [ ] **Phân quyền strict V2** — vẫn loose UAT. Sau confirm V2 flow → list/inbox/detail filter actor scope.
|
||||
- [ ] **schema-diagram §16 PE Level Opinions V2 + §17-21 Mig 18-21** — defer cron audit 2026-06-01.
|
||||
- [ ] **Skill `ef-core-migration` frontmatter** "21 migration" stale (thực 26). Defer cron audit 2026-06-01.
|
||||
- [ ] **Skill `dependency-audit-erp`** count stale. Defer cron audit 2026-06-01.
|
||||
|
||||
### ✅ Session 18 done (2026-05-08 19:45) — PE V2 polish + Clone B + Mig 25 IsUserSelectable + 4 bug fix UAT (7 commit `aaa1c6c` → `32a8d4d`)
|
||||
|
||||
User UAT live tiếp Session 17, chuỗi polish nhỏ + clone V2 cho type B. Áp memory `feedback_uat_skip_verify` (skip dotnet test mỗi chunk, push ngay) + lesson rename/remove → bắt buộc `npm run build`.
|
||||
|
||||
@ -0,0 +1,229 @@
|
||||
# Session 19 — 2026-05-09 (00:00 → ~04:00) — PE Section 5 V2 dynamic theo ApprovalWorkflowLevel + Mig 26
|
||||
|
||||
**Dev:** Claude
|
||||
**Duration:** ~4h
|
||||
**Base commit:** `daad79d` (Session 18 wrap-up docs)
|
||||
**Final HEAD:** `<Chunk D>`
|
||||
**Commits:** 5 (1 polish + 3 chunk per-commit + 1 docs)
|
||||
|
||||
## Bối cảnh
|
||||
|
||||
Session bắt đầu với template start: đọc 5 MD core + memory + cron audit status + CI status. Sau đó user UAT live tiếp Session 18:
|
||||
|
||||
1. Polish 3 button "Hành động" Workflow Panel (rút gọn label + 3 màu + bold) — `873e7a1`
|
||||
2. **Feature mới:** Section 5 "Ý kiến 4 phòng ban" hiện CỨNG 4 box (Mig 15 PheDuyet/CCM/MuaHàng/SmPm từ Phase 8) → **động theo Workflow V2 đã pin**: forEach Step → forEach Level → forEach NV → 1 OpinionBox với ý kiến + tên người. "Bước 1 phòng A có 2 NV → 2 box ngang hàng".
|
||||
|
||||
## Polish 3 button (`873e7a1`) — Hành động Workflow Panel
|
||||
|
||||
User screenshot turn cuối Session 18: "Hành động: Trả lại / Hủy/Từ chối / Duyệt → Chờ CCM" → yêu cầu rút gọn + 3 màu khác nhau + bold.
|
||||
|
||||
**PeWorkflowPanel.tsx (mirror fe-admin + fe-user):**
|
||||
|
||||
| Trước | Sau |
|
||||
|---|---|
|
||||
| `← Trả lại (về Drafter sửa)` (red) | **`← Trả lại`** (amber) |
|
||||
| `✗ Hủy / Từ chối` (red) | **`✗ Từ chối`** (red) |
|
||||
| `✓ Duyệt → Chờ CCM` (brand) | **`✓ Duyệt`** (emerald) |
|
||||
|
||||
- Phase đích vẫn hiện qua tooltip title khi hover button Duyệt (KHÔNG mất thông tin)
|
||||
- font-medium → font-bold
|
||||
- Logic disabled (V2 actor không trong cấp) giữ nguyên
|
||||
- Verify: `npm run build` × 2 pass · 0 TS error
|
||||
|
||||
## Feature lớn — Section 5 V2 dynamic + Mig 26
|
||||
|
||||
### Spec chốt 5 câu Q&A trước code
|
||||
|
||||
Theo memory `feedback_drastic_refactor_scope` — feature lớn cần spec rõ trước. User trả lời:
|
||||
|
||||
| Q | Chốt | Implement |
|
||||
|---|---|---|
|
||||
| Q1 | **1B** | Service V2 sau approve sync auto UPSERT vào LevelOpinion (Section 5 read-only summary) |
|
||||
| Q2 | **2A + Admin** | Chỉ NV chính chủ duyệt được. Admin override → lưu `SignedByUserId = admin.Id` (FE banner "duyệt thay") |
|
||||
| Q3 | **chuyển V2 hết** | Phiếu V1 fallback render Mig 15 4 box CỨNG readOnly. Mig 15 KHÔNG drop (giữ data legacy) |
|
||||
| Q4 | **4C + bonus** | Phase=DaDuyet/TuChoi → khoá hoàn toàn. Admin có quyền duyệt thay. Comment empty → ghi `"(duyệt — không ý kiến)"` |
|
||||
| Q5 | **5A** | Group "Bước N — Phòng X", grid-cols-2 cho N Cấp |
|
||||
|
||||
### Approach pick
|
||||
|
||||
Sau phân tích 3 approach (A=bảng mới, B=extend Mig 15, C=VIEW từ Approvals), pick **Approach A** — bảng mới riêng cho V2:
|
||||
- Clean V1/V2 separation (giống pattern V2 schema Mig 22-24)
|
||||
- Backward compat 100% V1 phiếu cũ
|
||||
- 1 row UNIQUE per (PE × Level) — latest write wins khi resubmit qua TraLai
|
||||
|
||||
### Chunk A (`77a3058`) — Domain + Mig 26 + EF
|
||||
|
||||
**Entity `PurchaseEvaluationLevelOpinion : AuditableEntity`:**
|
||||
```csharp
|
||||
public Guid PurchaseEvaluationId { get; set; }
|
||||
public Guid ApprovalWorkflowLevelId { get; set; }
|
||||
public string? Comment { get; set; } // max 2000
|
||||
public DateTime SignedAt { get; set; } // luôn có khi UPSERT
|
||||
public Guid SignedByUserId { get; set; } // NV chính chủ HOẶC Admin
|
||||
public string SignedByFullName { get; set; } // denorm — tránh user xóa/đổi tên
|
||||
```
|
||||
|
||||
**EF Configuration:**
|
||||
- UNIQUE composite (PEId, LevelId)
|
||||
- FK Cascade Pe (xoá phiếu → xoá opinions)
|
||||
- FK Restrict Level (admin xoá Level chặn nếu opinion tồn tại — bảo vệ data)
|
||||
- SignedByUserId KHÔNG nav (denorm only, tránh cascade khi xoá user)
|
||||
|
||||
**Migration 26 `AddPeLevelOpinionsForV2`:**
|
||||
- 1 CREATE TABLE `PurchaseEvaluationLevelOpinions`
|
||||
- 2 FK (Cascade Pe + Restrict Level)
|
||||
- 2 index (UNIQUE composite + IX LevelId)
|
||||
- 3-file rule commit đủ (.cs + Designer + Snapshot)
|
||||
|
||||
**Apply LocalDB SolutionErp_Dev OK** qua `dotnet ef database update --connection ...`. Mig 25 + 26 catchup (Mig 25 từ S18 trên `_Design`, _Dev catch up turn này).
|
||||
|
||||
**Verify:** dotnet build pass + dotnet test 81 pass (no regression).
|
||||
|
||||
### Chunk B (`90baa8e`) — Service V2 hook + DTO + GET include
|
||||
|
||||
**Service `PurchaseEvaluationWorkflowService.ApproveV2Async`** sau line `db.PurchaseEvaluationApprovals.Add(...)` chèn block UPSERT opinion:
|
||||
|
||||
```csharp
|
||||
// Mig 26 — UPSERT opinion vào row Level chính chủ. Section 5 FE render
|
||||
// dynamic theo flow.steps[].levels[]. Q1=1B chốt: comment khi duyệt auto
|
||||
// sync sang Section 5 (read-only summary).
|
||||
var matchingLevel = pendingLevelGroup
|
||||
.FirstOrDefault(l => actorUserId.HasValue && l.ApproverUserId == actorUserId.Value)
|
||||
?? pendingLevelGroup.First(); // Admin override fallback first
|
||||
var actorFullName = await ResolveActorFullNameAsync(actorUserId, isSystem, ct);
|
||||
var existingOpinion = await db.PurchaseEvaluationLevelOpinions
|
||||
.FirstOrDefaultAsync(o => o.PurchaseEvaluationId == evaluation.Id
|
||||
&& o.ApprovalWorkflowLevelId == matchingLevel.Id, ct);
|
||||
var normalizedComment = string.IsNullOrWhiteSpace(comment)
|
||||
? "(duyệt — không ý kiến)"
|
||||
: comment.Trim();
|
||||
// UPSERT: existing → update; null → Add new
|
||||
```
|
||||
|
||||
Reject (Trả lại / Từ chối) KHÔNG sync — giữ snapshot cũ nếu có.
|
||||
|
||||
Multi-NV cùng Cấp OR-of-N: match level theo `ApproverUserId == actorUserId`. Admin override → fallback first level group. FE detect SignedByUserId !== Level.ApproverUserId → banner "Admin duyệt thay".
|
||||
|
||||
**Helper `ResolveActorFullNameAsync(actorUserId, isSystem, ct)`:**
|
||||
- isSystem || null → "(System)"
|
||||
- User exists → FullName ?? UserName
|
||||
- User deleted → "(unknown)"
|
||||
|
||||
**DTO `PurchaseEvaluationLevelOpinionDto` (15 fields)** — denorm StepOrder/StepName/StepDepartmentId/StepDepartmentName/LevelOrder/LevelName/ApproverUserId/ApproverFullName + Comment/SignedAt/SignedByUserId/SignedByFullName.
|
||||
|
||||
**GET handler `GetPurchaseEvaluationQueryHandler`:**
|
||||
- Include LevelOpinions
|
||||
- Helper `BuildLevelOpinionsAsync(e, ct)` JOIN `ApprovalWorkflows.Steps.Levels` + Departments + Users → denorm DTO list. Empty list khi:
|
||||
- `e.LevelOpinions.Count == 0`, hoặc
|
||||
- `e.ApprovalWorkflowId == null` (V1 legacy)
|
||||
|
||||
**Verify:** dotnet build pass + dotnet test 81 pass.
|
||||
|
||||
### Chunk C (`6e913b3`) — FE Section 5 V2 dynamic mirror 2 app
|
||||
|
||||
**Type:** `PeLevelOpinion` (15 field) + `PeDetailBundle.levelOpinions[]`.
|
||||
|
||||
**Section 5 PeDetailTabs conditional render:**
|
||||
|
||||
```tsx
|
||||
<Section title="5. Ý kiến cấp duyệt (sign-off theo workflow)">
|
||||
{mode === 'workspace' && (
|
||||
<div className="...amber...">
|
||||
Ý kiến + chữ ký auto đồng bộ khi NV duyệt phiếu — vào menu "Duyệt" để ký.
|
||||
</div>
|
||||
)}
|
||||
{evaluation.approvalWorkflowId
|
||||
? <LevelOpinionsSectionV2 ev={evaluation} />
|
||||
: <DepartmentOpinionsSection ev={evaluation} readOnly={opinionsReadOnly} />}
|
||||
</Section>
|
||||
```
|
||||
|
||||
**`LevelOpinionsSectionV2`:**
|
||||
- Empty state khi `flow null` / `0 steps` → message "Workflow chưa cấu hình hoặc chưa có cấp duyệt nào"
|
||||
- forEach `step` → header "Bước N — <step.name>" + dept badge emerald + hint "(N người duyệt)" nếu totalApprovers > 1
|
||||
- Body grid-cols-2 cho `step.levels.flatMap(level => level.approvers.map(approver => <LevelOpinionBox/>))`
|
||||
- Lookup opinion theo (stepOrder, levelOrder, approverUserId) match levelOpinions[]
|
||||
|
||||
**`LevelOpinionBox` (read-only — Q1=1B):**
|
||||
- Title "Cấp N — <ApproverFullName>"
|
||||
- Badge amber "⚠ Admin <SignedByFullName> duyệt thay" khi `signedByUserId !== approverUserId`
|
||||
- Badge emerald "✓ Đã duyệt" khi opinion tồn tại
|
||||
- Empty: "— chưa duyệt" italic gray
|
||||
- Footer: timestamp signedAt format vi-VN
|
||||
|
||||
Workspace mode hint amber giữ — Drafter ở workspace tạo phiếu chưa duyệt nên Section 5 luôn empty.
|
||||
|
||||
Mirror fe-admin + fe-user (rule §3.9).
|
||||
|
||||
**Verify:** `npm run build` × 2 pass · 0 TS error.
|
||||
|
||||
### Chunk D — Docs (current)
|
||||
|
||||
5 file MD update:
|
||||
- `docs/STATUS.md` Recently Done top + DB tables 58→59 + migrations 25→26 + header narrative
|
||||
- `docs/HANDOFF.md` TL;DR Session 19 + 7 cảnh báo Session 20+ (giữ TL;DR S18 nguyên văn theo §6.5)
|
||||
- `CLAUDE.md` (root) count 25→26 mig + 58→59 tables + Mig 26 description block
|
||||
- `docs/changelog/migration-todos.md` Phase 9 Session 19 done section + Defer Session 20+ checklist
|
||||
- `docs/changelog/sessions/2026-05-09-0400-pe-section-5-v2-dynamic-mig26.md` (file này)
|
||||
|
||||
**KHÔNG đụng (per §6.5 không cố sửa khi không cần):**
|
||||
- `docs/rules.md` — không có rule mới
|
||||
- `docs/architecture.md` — không có changes structural
|
||||
- `docs/PROJECT-MAP.md` — không có structural changes
|
||||
- `docs/workflow-contract.md` — Contract V2 chưa wire (S20+)
|
||||
- `docs/forms-spec.md` — không liên quan
|
||||
- `docs/database/schema-diagram.md` — defer cron audit 2026-06-01 (cùng §17-21 đã defer trước)
|
||||
- `docs/database/database-guide.md` — count migration không hardcode trong header
|
||||
- Skills (6) — drift "21 migration" / "44 gotcha" defer cron audit 2026-06-01
|
||||
|
||||
## E2E verified
|
||||
|
||||
- ✅ `dotnet build SolutionErp.slnx` 0 error · 2 warning (CS8602 DocxRenderer cũ, không liên quan)
|
||||
- ✅ `dotnet test SolutionErp.slnx` **81 pass** (58 Domain + 23 Infra) — no regression
|
||||
- ✅ `dotnet ef migrations add AddPeLevelOpinionsForV2` 3-file rule OK
|
||||
- ✅ Mig 26 apply LocalDB SolutionErp_Dev OK (Mig 25 + 26 catchup)
|
||||
- ✅ `npm run build` × fe-admin + fe-user pass mỗi commit có FE changes
|
||||
- ✅ CI deploy commit `873e7a1` (polish 3 button) success run #168 lúc 02:03:57+07:00
|
||||
- ⏳ CI deploy commit `77a3058` + `90baa8e` + `6e913b3` (chờ verify run #169-171)
|
||||
- ⏸️ User UAT live test (defer cho user xác nhận)
|
||||
|
||||
## Bug gặp + fix
|
||||
|
||||
| Bug | Fix |
|
||||
|---|---|
|
||||
| Powershell working dir lệch lên 1 cấp khi `Push-Location fe-admin` | Dùng absolute path `D:\Dropbox\...\fe-admin` |
|
||||
| Bash `Select-Object` không tồn tại | Chuyển sang PowerShell tool đúng cú pháp |
|
||||
| Edit fe-user files fail "File has not been read yet" | Read trước Edit (rule harness) |
|
||||
|
||||
## Docs updates
|
||||
|
||||
(Liệt kê ở section Chunk D trên — 5 file MD).
|
||||
|
||||
## Memory updates
|
||||
|
||||
KHÔNG add memory mới turn này. Pattern Q&A spec trước code đã có memory `feedback_drastic_refactor_scope` đề cập (dedicated session conservative scope) — không tạo memory mới.
|
||||
|
||||
## Handoff to Session 20+
|
||||
|
||||
Đọc `docs/HANDOFF.md` "## ⚠️ Điều quan trọng cho Session 20+" cho 7 cảnh báo đầy đủ.
|
||||
|
||||
Top priorities:
|
||||
1. **Test V2 Service wire mới** (Chunk B Service hook) — Domain test ApproveV2 + UPSERT opinion match logic + Admin override
|
||||
2. **Contract V2 wire (Mig 27/28)** — mirror PE pattern: Contract.ApprovalWorkflowId + ContractLevelOpinions Mig 28 + Service ApproveV2Async + ContractDetailContent Section 5 V2
|
||||
3. **Phân quyền strict V2** — list/inbox/detail filter actor scope (giải bug /inbox loose)
|
||||
|
||||
## Thông số cumulative
|
||||
|
||||
| | Trước S19 | Sau S19 | Δ |
|
||||
|---|---:|---:|---:|
|
||||
| Migrations | 25 | **26** | +1 |
|
||||
| DB tables | 58 | **59** | +1 |
|
||||
| API endpoints | ~141 | ~141 | 0 (UPSERT auto qua Service hook không endpoint) |
|
||||
| FE pages | 33 | 33 | 0 (modify existing only) |
|
||||
| Test pass | 81 | 81 | 0 (UAT defer test §7) |
|
||||
| Gotchas | 44 | 44 | 0 |
|
||||
| Memory entries | 14 | 14 | 0 |
|
||||
| Skills | 6 | 6 | 0 |
|
||||
| Commits | (after S18) | **+4** | 873e7a1 (polish) + 77a3058 (Chunk A) + 90baa8e (Chunk B) + 6e913b3 (Chunk C) + Chunk D Docs (current) |
|
||||
| BE LOC | ~16200 | ~16400 | +~200 (Mig 26 + Service hook + DTO + GET helper) |
|
||||
| FE LOC | ~17600 | ~17900 | +~300 (Section 5 V2 dynamic + LevelOpinionBox + types mirror 2 app) |
|
||||
Reference in New Issue
Block a user