690329:2252 Fixing refactor Correspondence GPT-5.3-Codex #05
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
---
|
||||
always_on: true
|
||||
---
|
||||
|
||||
# NAP-DMS Project Context
|
||||
|
||||
## Role & Persona
|
||||
|
||||
Act as a **Senior Full Stack Developer** specialized in:
|
||||
|
||||
- NestJS, Next.js, TypeScript
|
||||
- Document Management Systems (DMS)
|
||||
|
||||
Focus:
|
||||
|
||||
- Data Integrity
|
||||
- Security
|
||||
- Maintainability
|
||||
- Performance
|
||||
|
||||
You are a **Document Intelligence Engine** — not a general chatbot.
|
||||
Every response must be **precise**, **spec-compliant**, and **production-ready**.
|
||||
|
||||
## Project Information
|
||||
|
||||
- **Project:** NAP-DMS (LCBP3)
|
||||
- **Version:** 1.8.5
|
||||
- **Stack:** NestJS + Next.js + TypeScript + MariaDB
|
||||
- **Repo:** https://git.np-dms.work/np-dms/lcbp3
|
||||
|
||||
## Rule Enforcement Tiers
|
||||
|
||||
### 🔴 Tier 1 — CRITICAL (CI BLOCKER)
|
||||
|
||||
Build fails immediately if violated:
|
||||
|
||||
- Security (Auth, RBAC, Validation)
|
||||
- UUID Strategy (ADR-019) — no `parseInt` / `Number` / `+` on UUID
|
||||
- Database correctness — verify schema before writing queries
|
||||
- File upload security (ClamAV + whitelist)
|
||||
- AI validation boundary (ADR-018)
|
||||
- Forbidden patterns: `any`, `console.log`, UUID misuse
|
||||
|
||||
### 🟡 Tier 2 — IMPORTANT (CODE REVIEW)
|
||||
|
||||
Must fix before merge:
|
||||
|
||||
- Architecture patterns (thin controller, business logic in service)
|
||||
- Test coverage (80%+ business logic, 70%+ backend overall)
|
||||
- Cache invalidation
|
||||
- Naming conventions
|
||||
|
||||
### 🟢 Tier 3 — GUIDELINES
|
||||
|
||||
Best practice — follow when possible:
|
||||
|
||||
- Code style / formatting (Prettier handles)
|
||||
- Comment completeness
|
||||
- Minor optimizations
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
always_on: true
|
||||
---
|
||||
|
||||
# ADR-019 UUID Strategy
|
||||
|
||||
## CRITICAL RULES
|
||||
|
||||
- **NEVER** use `parseInt()` on UUID values
|
||||
- **NEVER** use `Number()` on UUID values
|
||||
- **NEVER** use `+` operator on UUID values
|
||||
- **ALWAYS** use `publicId` (string UUID) for API responses
|
||||
- **NEVER** expose internal INT `id` in API responses (use `@Exclude()`)
|
||||
|
||||
## Identifier Types
|
||||
|
||||
| Context | Type | Notes |
|
||||
| ---------------- | ------------------------- | ------------------------------------------- |
|
||||
| Internal / DB FK | `INT AUTO_INCREMENT` | Never exposed in API |
|
||||
| Public API / URL | `UUIDv7` (MariaDB native) | Stored as BINARY(16), no transformer needed |
|
||||
| Entity Property | `publicId: string` | Exposed directly in API (no transformation) |
|
||||
| API Response | `publicId: string` (UUID) | INT `id` has `@Exclude()` — never appears |
|
||||
|
||||
## Backend Pattern (NestJS/TypeORM)
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Entity()
|
||||
class Project extends UuidBaseEntity {
|
||||
@Column({ type: 'uuid' })
|
||||
publicId: string; // UUID string, no transformation needed
|
||||
|
||||
@PrimaryKey()
|
||||
@Exclude()
|
||||
id: number; // Internal INT, never exposed
|
||||
}
|
||||
|
||||
// API Response → { id: "019505a1-7c3e-7000-8000-abc123def456" }
|
||||
// Uses publicId directly, no @Expose({ name: 'id' }) needed
|
||||
```
|
||||
|
||||
## Frontend Pattern (Next.js)
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT — Use publicId only
|
||||
type ProjectOption = {
|
||||
publicId?: string; // No uuid, no id fallback
|
||||
projectName?: string;
|
||||
};
|
||||
|
||||
// ❌ WRONG — Multiple identifiers cause confusion
|
||||
type ProjectOption = {
|
||||
publicId?: string;
|
||||
uuid?: string; // Don't do this
|
||||
id?: number; // Don't do this
|
||||
};
|
||||
|
||||
// ❌ NEVER use parseInt on UUID
|
||||
parseInt(projectId); // "0195..." → 19 (WRONG!)
|
||||
|
||||
// ❌ NEVER use id ?? '' fallback
|
||||
const value = c.publicId ?? c.id ?? ''; // Wrong!
|
||||
|
||||
// ✅ CORRECT — Use publicId only
|
||||
const value = c.publicId; // "019505a1-7c3e-7000-8000-abc123def456"
|
||||
```
|
||||
|
||||
## Related Documents
|
||||
|
||||
- `specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md`
|
||||
- `specs/05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md`
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
always_on: true
|
||||
---
|
||||
|
||||
# Security Rules (Non-Negotiable)
|
||||
|
||||
## Mandatory Security Requirements
|
||||
|
||||
1. **Idempotency:** All critical `POST`/`PUT`/`PATCH` MUST validate `Idempotency-Key` header
|
||||
2. **Two-Phase File Upload:** Upload → Temp → Commit → Permanent
|
||||
3. **Race Conditions:** Redis Redlock + TypeORM `@VersionColumn` for Document Numbering
|
||||
4. **Validation:** Zod (frontend) + class-validator (backend DTO)
|
||||
5. **Password:** bcrypt 12 salt rounds, min 8 chars, rotate every 90 days
|
||||
6. **Rate Limiting:** `ThrottlerGuard` on all auth endpoints
|
||||
7. **File Upload:** Whitelist PDF/DWG/DOCX/XLSX/ZIP, max 50MB, ClamAV scan
|
||||
8. **AI Isolation (ADR-018):** Ollama on Admin Desktop ONLY — NO direct DB/storage access
|
||||
|
||||
## Full Documentation
|
||||
|
||||
`specs/06-Decision-Records/ADR-016-security-authentication.md`
|
||||
|
||||
## Security Checklist (Before Every Commit)
|
||||
|
||||
- [ ] Input validation implemented (Zod/class-validator)
|
||||
- [ ] RBAC/CASL permissions checked
|
||||
- [ ] No SQL injection vulnerabilities
|
||||
- [ ] File upload validation (whitelist + ClamAV)
|
||||
- [ ] Rate limiting applied to auth endpoints
|
||||
- [ ] OWASP Top 10 review passed
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
always_on: true
|
||||
---
|
||||
|
||||
# TypeScript Rules
|
||||
|
||||
## Strict Requirements
|
||||
|
||||
- **Strict Mode** — all strict checks enforced
|
||||
- **ZERO `any` types** — use proper types or `unknown` + narrowing
|
||||
- **ZERO `console.log`** — NestJS `Logger` (backend); remove before commit (frontend)
|
||||
|
||||
## Comment Language Policy
|
||||
|
||||
- **Comments:** Thai (เข้าใจง่ายสำหรับทีมไทย)
|
||||
- **Code Identifiers:** English (variables, functions, classes)
|
||||
|
||||
## Error Handling Pattern
|
||||
|
||||
```typescript
|
||||
// Backend (NestJS)
|
||||
import { Logger } from '@nestjs/common';
|
||||
const logger = new Logger('ServiceName');
|
||||
|
||||
// Use logger instead of console.log
|
||||
logger.error('Error message', error.stack);
|
||||
throw new HttpException('Message', HttpStatus.BAD_REQUEST);
|
||||
|
||||
// Frontend (Next.js)
|
||||
// Remove all console.log before commit
|
||||
// Use proper error boundaries and toast notifications
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
always_on: true
|
||||
---
|
||||
|
||||
# Domain Terminology
|
||||
|
||||
## DMS Glossary
|
||||
|
||||
| ✅ Use | ❌ Don't Use |
|
||||
| ------------------ | ------------------------------------- |
|
||||
| Correspondence | Letter, Communication, Document |
|
||||
| RFA | Approval Request, Submit for Approval |
|
||||
| Transmittal | Delivery Note, Cover Letter |
|
||||
| Circulation | Distribution, Routing |
|
||||
| Shop Drawing | Construction Drawing |
|
||||
| Contract Drawing | Design Drawing, Blueprint |
|
||||
| Workflow Engine | Approval Flow, Process Engine |
|
||||
| Document Numbering | Document ID, Auto Number |
|
||||
| RBAC | Permission System (generic) |
|
||||
|
||||
## Full Glossary
|
||||
|
||||
`specs/00-overview/00-02-glossary.md`
|
||||
|
||||
## Key Spec Files Priority
|
||||
|
||||
Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others
|
||||
|
||||
| Document | Path | Use When |
|
||||
| ----------------------- | ----------------------------------------------------------------- | ------------------------------- |
|
||||
| **Glossary** | `specs/00-overview/00-02-glossary.md` | Verify domain terminology |
|
||||
| **Schema Tables** | `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | Before writing any query |
|
||||
| **Data Dictionary** | `specs/03-Data-and-Storage/03-01-data-dictionary.md` | Field meanings + business rules |
|
||||
| **Edge Cases** | `specs/01-Requirements/01-06-edge-cases-and-rules.md` | Prevent bugs in flows |
|
||||
| **ADR-019 UUID** | `specs/06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | UUID-related work |
|
||||
| **Backend Guidelines** | `specs/05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns |
|
||||
| **Frontend Guidelines** | `specs/05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns |
|
||||
| **Testing Strategy** | `specs/05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals |
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
always_on: true
|
||||
---
|
||||
|
||||
# Forbidden Actions
|
||||
|
||||
## ❌ Never Do This
|
||||
|
||||
| ❌ Forbidden | ✅ Correct Approach |
|
||||
| ----------------------------------------------- | --------------------------------------------- |
|
||||
| SQL Triggers for business logic | NestJS Service methods |
|
||||
| `.env` files in production | `docker-compose.yml` environment section |
|
||||
| TypeORM migration files | Edit schema SQL directly (ADR-009) |
|
||||
| Inventing table/column names | Verify against `schema-02-tables.sql` |
|
||||
| `any` TypeScript type | Proper types / generics |
|
||||
| `console.log` in committed code | NestJS Logger (backend) / remove (frontend) |
|
||||
| `req: any` in controllers | `RequestWithUser` typed interface |
|
||||
| `parseInt()` on UUID values | Use UUID string directly (ADR-019) |
|
||||
| Exposing INT PK in API responses | UUIDv7 (ADR-019) |
|
||||
| AI accessing DB/storage directly | AI → DMS API → DB (ADR-018) |
|
||||
| Direct file operations bypassing StorageService | `StorageService` for all file moves |
|
||||
| Inline email/notification sending | BullMQ queue job |
|
||||
| Deploying without Release Gates | Complete `04-08-release-management-policy.md` |
|
||||
|
||||
## Schema Changes (ADR-009)
|
||||
|
||||
- **NO TypeORM migrations** — edit SQL schema directly
|
||||
- Always check `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` before writing queries
|
||||
- Update Data Dictionary when changing fields
|
||||
|
||||
## UUID Handling
|
||||
|
||||
See `01-adr-019-uuid.md` for complete UUID rules.
|
||||
|
||||
Quick reminder:
|
||||
- ❌ `parseInt(uuid)` → NEVER
|
||||
- ❌ `Number(uuid)` → NEVER
|
||||
- ✅ Use UUID string directly
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
trigger: glob
|
||||
globs:
|
||||
- "backend/**/*.service.ts"
|
||||
- "backend/**/*.controller.ts"
|
||||
- "backend/**/*.dto.ts"
|
||||
- "backend/**/*.entity.ts"
|
||||
---
|
||||
|
||||
# Backend Patterns (NestJS)
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Thin Controller** — business logic in Service layer
|
||||
- **DTO Validation** — class-validator + class-transformer
|
||||
- **RBAC** — CASL for authorization
|
||||
- **Error Handling** — Logger + HttpException
|
||||
|
||||
## UUID Resolution Pattern
|
||||
|
||||
```typescript
|
||||
// Controller - accept UUID in DTO
|
||||
@Post()
|
||||
async create(@Body() dto: CreateCorrespondenceDto) {
|
||||
// Resolve UUID to internal ID
|
||||
const contract = await this.contractService.findOneByUuid(dto.contractUuid);
|
||||
const contractId = contract.id; // Internal INT for DB queries
|
||||
|
||||
return this.service.create(dto, contractId);
|
||||
}
|
||||
|
||||
// Service - use internal ID for DB operations
|
||||
async create(dto: CreateCorrespondenceDto, contractId: number) {
|
||||
// Use contractId (INT) for database queries
|
||||
const correspondence = this.repo.create({
|
||||
contractId, // FK is INT
|
||||
// ... other fields
|
||||
});
|
||||
return this.repo.save(correspondence);
|
||||
}
|
||||
```
|
||||
|
||||
## API Response Pattern
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Entity()
|
||||
class Contract extends UuidBaseEntity {
|
||||
@Column({ type: 'uuid' })
|
||||
publicId: string;
|
||||
|
||||
@PrimaryKey()
|
||||
@Exclude()
|
||||
id: number;
|
||||
}
|
||||
|
||||
// Response automatically includes publicId as 'id'
|
||||
// { id: "019505a1-7c3e-7000-8000-abc123def456", ... }
|
||||
```
|
||||
|
||||
## Full Guidelines
|
||||
|
||||
`specs/05-Engineering-Guidelines/05-02-backend-guidelines.md`
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
trigger: glob
|
||||
globs:
|
||||
- "frontend/**/*.tsx"
|
||||
- "frontend/**/*.ts"
|
||||
- "frontend/**/*.css"
|
||||
---
|
||||
|
||||
# Frontend Patterns (Next.js)
|
||||
|
||||
## Form Handling
|
||||
|
||||
- **RHF** (React Hook Form) for form management
|
||||
- **Zod** for validation schema
|
||||
- **TanStack Query** for server state
|
||||
|
||||
## UUID Handling
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT - Use publicId only
|
||||
interface ProjectOption {
|
||||
publicId?: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
// Select options
|
||||
const options = contracts.map(c => ({
|
||||
label: `${c.contractName} (${c.contractCode})`,
|
||||
value: c.publicId!, // Use publicId, no fallback to id
|
||||
}));
|
||||
|
||||
// ❌ WRONG - Never use these patterns
|
||||
const value = c.publicId ?? c.id ?? ''; // Wrong!
|
||||
const id = parseInt(projectId); // Wrong - parseInt on UUID!
|
||||
```
|
||||
|
||||
## API Client Pattern
|
||||
|
||||
```typescript
|
||||
// Use publicId directly in API calls
|
||||
const contract = await contractService.getById(publicId);
|
||||
|
||||
// Form submission with UUID
|
||||
const onSubmit = async (data: FormData) => {
|
||||
await correspondenceService.create({
|
||||
contractUuid: selectedContract.publicId!, // UUID string
|
||||
// ... other fields
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## Full Guidelines
|
||||
|
||||
`specs/05-Engineering-Guidelines/05-03-frontend-guidelines.md`
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
always_on: true
|
||||
---
|
||||
|
||||
# Development Flow
|
||||
|
||||
## 🔴 Critical Work — DB / API / Security / Workflow Engine
|
||||
|
||||
**MUST complete all steps:**
|
||||
|
||||
1. **Glossary check** — verify domain terms in `00-02-glossary.md`
|
||||
2. **Read the spec** — select from Key Spec Files table
|
||||
3. **Check schema** — verify table/column in `schema-02-tables.sql`
|
||||
4. **Check data dictionary** — confirm field meanings + business rules
|
||||
5. **Scan edge cases** — `01-06-edge-cases-and-rules.md`
|
||||
6. **Check ADRs** — verify decisions align (ADR-009, ADR-018, ADR-019)
|
||||
7. **Write code** — TypeScript strict, no `any`, no `console.log`
|
||||
|
||||
## 🟡 Normal Work — UI / Feature / Integration
|
||||
|
||||
- Follow existing patterns in codebase
|
||||
- Check spec for relevant module only
|
||||
- No need to read all specs
|
||||
|
||||
## 🟢 Quick Fix — Bug Fix / Typo / Style
|
||||
|
||||
- Fix directly
|
||||
- Add minimal test if logic changed
|
||||
- Check forbidden patterns before commit
|
||||
|
||||
## Context-Aware Triggers
|
||||
|
||||
| Request | Files to Check | Expected Response |
|
||||
| -------------------- | ------------------------------------------------------- | --------------------------------------------------- |
|
||||
| "สร้าง API ใหม่" | `05-02-backend-guidelines.md`, `schema-02-tables.sql` | NestJS Controller + Service + DTO + CASL Guard |
|
||||
| "แก้ฟอร์ม frontend" | `05-03-frontend-guidelines.md`, `01-06-edge-cases.md` | RHF+Zod + TanStack Query + Thai comments |
|
||||
| "เพิ่ม field ใหม่" | `ADR-009`, `data-dictionary.md`, `schema-02-tables.sql` | Edit SQL directly + update Data Dictionary + Entity |
|
||||
| "ตรวจสอบ UUID" | `ADR-019`, `05-07-hybrid-uuid-implementation-plan.md` | UUIDv7 MariaDB native UUID + TransformInterceptor |
|
||||
| "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | Edit SQL schema directly + n8n workflow |
|
||||
| "ตรวจสอบ permission" | `seed-permissions.sql`, `ADR-016` | CASL 4-Level RBAC matrix |
|
||||
| "deploy production" | `04-08-release-management-policy.md`, `ADR-015` | Release Gates + Blue-Green strategy |
|
||||
| "เพิ่ม test" | `05-04-testing-strategy.md` | Coverage goals + test patterns |
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
always_on: true
|
||||
---
|
||||
|
||||
# Commit Checklist
|
||||
|
||||
## Pre-Commit Verification
|
||||
|
||||
- [ ] UUID pattern verified (no parseInt on UUID)
|
||||
- [ ] No `any` types in TypeScript
|
||||
- [ ] No `console.log` in committed code
|
||||
- [ ] Comments in Thai
|
||||
- [ ] Code identifiers in English
|
||||
- [ ] Schema changes via SQL directly (not migration)
|
||||
- [ ] Test coverage meets targets (Backend 70%+, Business Logic 80%+)
|
||||
- [ ] Relevant ADRs checked (ADR-009, ADR-018, ADR-019)
|
||||
- [ ] Glossary terms used correctly
|
||||
- [ ] Error handling complete (Logger + HttpException)
|
||||
- [ ] i18n keys used instead of hardcode text
|
||||
- [ ] Cache invalidation when data modified
|
||||
- [ ] Security checklist passed (OWASP Top 10)
|
||||
|
||||
## Commit Message Format
|
||||
|
||||
```
|
||||
type(scope): description
|
||||
|
||||
[optional body]
|
||||
```
|
||||
|
||||
Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
||||
|
||||
Examples:
|
||||
- `feat(correspondence): add originator organization validation`
|
||||
- `fix(uuid): correct parseInt usage to string comparison`
|
||||
- `spec(agents): bump to v1.8.5 - refactor structure`
|
||||
@@ -57,6 +57,9 @@ ENV AUTH_URL=${AUTH_URL}
|
||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Disable Turbopack to avoid "No child process" panic in Docker container
|
||||
ENV NEXT_TURBOPACK=0
|
||||
|
||||
# WORKAROUND: QNAP overlayfs fails with "Unknown system error -10" on deeply
|
||||
# nested App Router paths. Redirect .next output to ultra-short root path /n
|
||||
# to minimise overlay nesting depth, then move back after build completes.
|
||||
|
||||
@@ -25,6 +25,15 @@ interface CorrespondenceDetailProps {
|
||||
selectedRevisionId?: string;
|
||||
}
|
||||
|
||||
const normalizeUuid = (value?: string): string | undefined => {
|
||||
if (typeof value !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalized = value.trim().toLowerCase();
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
};
|
||||
|
||||
export function CorrespondenceDetail({ data, selectedRevisionId }: CorrespondenceDetailProps) {
|
||||
const submitMutation = useSubmitCorrespondence();
|
||||
const processMutation = useProcessWorkflow();
|
||||
@@ -36,8 +45,9 @@ export function CorrespondenceDetail({ data, selectedRevisionId }: Correspondenc
|
||||
|
||||
if (!data) return <div>No data found</div>;
|
||||
|
||||
const selectedRevision = selectedRevisionId
|
||||
? data.revisions?.find((r) => r.publicId === selectedRevisionId)
|
||||
const normalizedSelectedRevisionId = normalizeUuid(selectedRevisionId);
|
||||
const selectedRevision = normalizedSelectedRevisionId
|
||||
? data.revisions?.find((r) => normalizeUuid(r.publicId) === normalizedSelectedRevisionId)
|
||||
: undefined;
|
||||
const currentRevision = selectedRevision || data.revisions?.find((r) => r.isCurrent) || data.revisions?.[0];
|
||||
const subject = currentRevision?.subject || '-';
|
||||
|
||||
@@ -125,6 +125,11 @@ const normalizePublicId = (value: unknown): string | undefined => {
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
};
|
||||
|
||||
const normalizeUuid = (value: unknown): string | undefined => {
|
||||
const normalized = normalizePublicId(value);
|
||||
return normalized ? normalized.toLowerCase() : undefined;
|
||||
};
|
||||
|
||||
export function CorrespondenceForm({
|
||||
initialData,
|
||||
uuid,
|
||||
@@ -147,8 +152,9 @@ export function CorrespondenceForm({
|
||||
const correspondenceTypes = extractArrayData<CorrespondenceTypeOption>(correspondenceTypesData);
|
||||
|
||||
// Extract initial values if editing
|
||||
const selectedRevision = selectedRevisionId
|
||||
? initialData?.revisions?.find((r) => normalizePublicId(r.publicId) === selectedRevisionId)
|
||||
const normalizedSelectedRevisionId = normalizeUuid(selectedRevisionId);
|
||||
const selectedRevision = normalizedSelectedRevisionId
|
||||
? initialData?.revisions?.find((r) => normalizeUuid(r.publicId) === normalizedSelectedRevisionId)
|
||||
: undefined;
|
||||
const currentRev = selectedRevision || initialData?.revisions?.find((r) => r.isCurrent) || initialData?.revisions?.[0];
|
||||
const initialToRecipient = initialData?.recipients?.find((r) => r.recipientType === 'TO');
|
||||
|
||||
Reference in New Issue
Block a user