task-i
This commit is contained in:
475
plans/task-i.md
Normal file
475
plans/task-i.md
Normal file
@@ -0,0 +1,475 @@
|
||||
# Task I: Storage Abstraction + Approved Artifact Lifecycle
|
||||
|
||||
## ALLA OS CRM vNext
|
||||
|
||||
คุณคือ Senior Full-stack Engineer
|
||||
|
||||
ให้ implement storage abstraction และ approved artifact lifecycle สำหรับ Quotation PDF โดยต่อยอดจาก Task H และ H.1
|
||||
|
||||
---
|
||||
|
||||
## Must Read
|
||||
|
||||
```txt
|
||||
docs/implementation/technical-debt.md
|
||||
docs/adr/0008-attachment-storage-strategy.md
|
||||
docs/adr/0009-approved-document-storage-lifecycle.md
|
||||
|
||||
src/features/foundation/pdf-generator/**
|
||||
src/features/foundation/document-template/**
|
||||
src/features/crm/quotations/document/**
|
||||
src/features/crm/quotations/**
|
||||
src/app/api/crm/quotations/[id]/approved-pdf/route.ts
|
||||
src/db/schema.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Goal
|
||||
|
||||
ทำให้ PDF storage พร้อม production มากขึ้น
|
||||
|
||||
จากเดิม:
|
||||
|
||||
```txt
|
||||
public/generated/quotations/<organizationId>/
|
||||
```
|
||||
|
||||
ไปเป็น:
|
||||
|
||||
```txt
|
||||
Storage Provider Interface
|
||||
Local Provider for dev
|
||||
S3 / MinIO compatible provider for production
|
||||
Secure download route
|
||||
Immutable approved artifact lifecycle
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Scope I1: Storage Provider Interface
|
||||
|
||||
สร้าง foundation module:
|
||||
|
||||
```txt
|
||||
src/features/foundation/storage/**
|
||||
```
|
||||
|
||||
Files:
|
||||
|
||||
```txt
|
||||
types.ts
|
||||
service.ts
|
||||
server/local-provider.ts
|
||||
server/s3-provider.ts
|
||||
server/provider.ts
|
||||
```
|
||||
|
||||
Interface:
|
||||
|
||||
```ts
|
||||
export interface StorageProvider {
|
||||
putObject(input: PutObjectInput): Promise<StoredObject>
|
||||
getObject(input: GetObjectInput): Promise<StoredObjectStream>
|
||||
deleteObject(input: DeleteObjectInput): Promise<void>
|
||||
objectExists(input: ObjectExistsInput): Promise<boolean>
|
||||
getSignedUrl?(input: GetSignedUrlInput): Promise<string>
|
||||
}
|
||||
```
|
||||
|
||||
Types:
|
||||
|
||||
```ts
|
||||
PutObjectInput
|
||||
GetObjectInput
|
||||
DeleteObjectInput
|
||||
ObjectExistsInput
|
||||
GetSignedUrlInput
|
||||
StoredObject
|
||||
StoredObjectStream
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* service ต้อง server-only
|
||||
* client ห้ามเข้าถึง provider โดยตรง
|
||||
* storage key ต้อง organization-scoped
|
||||
* ห้ามใช้ raw public path เป็น source of truth
|
||||
|
||||
---
|
||||
|
||||
# Scope I2: Storage Configuration
|
||||
|
||||
รองรับ env:
|
||||
|
||||
```txt
|
||||
STORAGE_PROVIDER=local | s3
|
||||
|
||||
LOCAL_STORAGE_ROOT=storage
|
||||
|
||||
S3_ENDPOINT=
|
||||
S3_REGION=
|
||||
S3_BUCKET=
|
||||
S3_ACCESS_KEY_ID=
|
||||
S3_SECRET_ACCESS_KEY=
|
||||
S3_FORCE_PATH_STYLE=true
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* local ใช้ได้ใน dev
|
||||
* s3 provider ต้อง compatible กับ MinIO/S3
|
||||
* ถ้า config ไม่ครบ ให้ throw error ชัดเจน
|
||||
* อย่า hardcode bucket/path
|
||||
|
||||
---
|
||||
|
||||
# Scope I3: Artifact Schema
|
||||
|
||||
เพิ่ม table ถ้ายังไม่มี:
|
||||
|
||||
```txt
|
||||
crm_document_artifacts
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
```txt
|
||||
id
|
||||
organizationId
|
||||
|
||||
entityType
|
||||
entityId
|
||||
|
||||
documentType
|
||||
artifactType
|
||||
|
||||
templateVersionId
|
||||
|
||||
storageProvider
|
||||
storageKey
|
||||
fileName
|
||||
contentType
|
||||
fileSize
|
||||
checksum
|
||||
|
||||
status
|
||||
|
||||
generatedBy
|
||||
generatedAt
|
||||
|
||||
lockedAt
|
||||
lockedBy
|
||||
|
||||
voidedAt
|
||||
voidedBy
|
||||
voidReason
|
||||
|
||||
metadata
|
||||
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
```
|
||||
|
||||
artifactType:
|
||||
|
||||
```txt
|
||||
preview
|
||||
approved_pdf
|
||||
export
|
||||
```
|
||||
|
||||
status:
|
||||
|
||||
```txt
|
||||
active
|
||||
locked
|
||||
voided
|
||||
deleted
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* approved_pdf ต้อง lock หลัง generate
|
||||
* approved_pdf ห้าม overwrite
|
||||
* ถ้าต้อง regenerate ให้สร้าง artifact ใหม่หรือ void artifact เดิมก่อนตาม policy
|
||||
* organizationId required
|
||||
|
||||
---
|
||||
|
||||
# Scope I4: Checksum
|
||||
|
||||
เพิ่ม utility:
|
||||
|
||||
```txt
|
||||
src/features/foundation/storage/server/checksum.ts
|
||||
```
|
||||
|
||||
รองรับ:
|
||||
|
||||
```ts
|
||||
sha256(buffer)
|
||||
```
|
||||
|
||||
ใช้บันทึก checksum ของ PDF artifact
|
||||
|
||||
---
|
||||
|
||||
# Scope I5: Approved PDF Storage Refactor
|
||||
|
||||
ปรับ `generateApprovedQuotationPdf()` จากการเขียน:
|
||||
|
||||
```txt
|
||||
public/generated/...
|
||||
```
|
||||
|
||||
เป็น:
|
||||
|
||||
```txt
|
||||
StorageProvider.putObject()
|
||||
crm_document_artifacts insert
|
||||
quotation.approvedPdfUrl update เป็น secure reference
|
||||
```
|
||||
|
||||
Recommended `approvedPdfUrl`:
|
||||
|
||||
```txt
|
||||
artifact:<artifactId>
|
||||
```
|
||||
|
||||
หรือถ้ามี field ใหม่:
|
||||
|
||||
```txt
|
||||
approvedArtifactId
|
||||
```
|
||||
|
||||
ถ้าต้องเพิ่ม field ใน quotation:
|
||||
|
||||
```txt
|
||||
approvedArtifactId
|
||||
```
|
||||
|
||||
ให้เพิ่มแบบระวัง
|
||||
|
||||
Rules:
|
||||
|
||||
* approved PDF generate ได้เฉพาะ quotation.status = approved
|
||||
* ถ้ามี locked approved artifact อยู่แล้ว ให้ไม่ overwrite
|
||||
* ถ้าต้อง regenerate ให้ return error พร้อมข้อความชัดเจน
|
||||
* create attachment metadata ให้ชี้ storageKey/artifactId
|
||||
* audit generate approved PDF
|
||||
|
||||
---
|
||||
|
||||
# Scope I6: Secure Download Route
|
||||
|
||||
ปรับ:
|
||||
|
||||
```txt
|
||||
GET /api/crm/quotations/[id]/approved-pdf
|
||||
```
|
||||
|
||||
ให้:
|
||||
|
||||
* อ่าน artifact จาก database
|
||||
* validate organizationId
|
||||
* validate permission
|
||||
* ดึงไฟล์ผ่าน StorageProvider.getObject()
|
||||
* stream PDF กลับ
|
||||
* ไม่ใช้ public file path ตรง ๆ
|
||||
|
||||
เพิ่ม route generic ได้ถ้าเหมาะสม:
|
||||
|
||||
```txt
|
||||
GET /api/crm/document-artifacts/[id]/download
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* ห้าม expose storageKey ถ้าไม่จำเป็น
|
||||
* ห้ามให้ user เดา URL แล้วโหลดได้
|
||||
* permission guard ต้องอยู่ server route
|
||||
* approved PDF download ควร audit ได้ ถ้าทำไม่ใหญ่เกินไป
|
||||
|
||||
---
|
||||
|
||||
# Scope I7: Artifact Lifecycle
|
||||
|
||||
Implement service:
|
||||
|
||||
```txt
|
||||
src/features/foundation/document-artifact/server/service.ts
|
||||
```
|
||||
|
||||
Functions:
|
||||
|
||||
```ts
|
||||
createArtifact()
|
||||
getArtifact()
|
||||
getActiveArtifactForEntity()
|
||||
lockArtifact()
|
||||
voidArtifact()
|
||||
deleteArtifactMetadataOnly()
|
||||
downloadArtifact()
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* `locked` artifact ห้ามแก้ storageKey/fileSize/checksum
|
||||
* `voided` artifact ยังเก็บ metadata
|
||||
* delete จริงยังไม่ต้องลบ object storage ถ้าไม่จำเป็น
|
||||
* approved PDF lifecycle ต้อง immutable-first
|
||||
|
||||
---
|
||||
|
||||
# Scope I8: UI Integration
|
||||
|
||||
Quotation detail:
|
||||
|
||||
* View Approved PDF ใช้ secure download route
|
||||
* ถ้ามี approved artifact แสดง:
|
||||
|
||||
* fileName
|
||||
* generatedAt
|
||||
* generatedBy
|
||||
* checksum short
|
||||
* locked badge
|
||||
* ถ้ายังไม่มี artifact แต่มี old approvedPdfUrl จาก Task H ให้แสดง migration warning
|
||||
|
||||
---
|
||||
|
||||
# Scope I9: Migration Compatibility
|
||||
|
||||
ถ้ามี approvedPdfUrl เดิมแบบ:
|
||||
|
||||
```txt
|
||||
/generated/...
|
||||
```
|
||||
|
||||
ให้ไม่ลบทิ้งทันที
|
||||
|
||||
เพิ่ม compatibility:
|
||||
|
||||
* ถ้า approvedPdfUrl เป็น artifact reference → ใช้ artifact
|
||||
* ถ้าเป็น legacy local path → แสดง legacy warning
|
||||
* future migration script ค่อย migrate legacy file
|
||||
|
||||
---
|
||||
|
||||
# Scope I10: Documentation
|
||||
|
||||
Update:
|
||||
|
||||
```txt
|
||||
docs/implementation/technical-debt.md
|
||||
docs/adr/0009-approved-document-storage-lifecycle.md
|
||||
```
|
||||
|
||||
เพิ่ม:
|
||||
|
||||
```txt
|
||||
Storage provider abstraction
|
||||
Local vs S3/MinIO policy
|
||||
Immutable artifact policy
|
||||
Legacy public/generated migration
|
||||
Download security model
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Permissions
|
||||
|
||||
เพิ่มถ้ายังไม่มี:
|
||||
|
||||
```txt
|
||||
crm.document_artifact.read
|
||||
crm.document_artifact.download
|
||||
crm.document_artifact.void
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Audit
|
||||
|
||||
Audit required:
|
||||
|
||||
```txt
|
||||
crm_document_artifact
|
||||
```
|
||||
|
||||
Actions:
|
||||
|
||||
```txt
|
||||
create
|
||||
lock
|
||||
void
|
||||
download
|
||||
```
|
||||
|
||||
ถ้า download audit ทำให้ระบบหนัก ให้ทำ optional toggle ได้
|
||||
|
||||
---
|
||||
|
||||
# ห้ามทำ
|
||||
|
||||
```txt
|
||||
Report
|
||||
Dashboard KPI
|
||||
Notification
|
||||
Template Designer
|
||||
Mapping Editor
|
||||
Full object-storage admin UI
|
||||
Bulk migration of legacy files
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Output
|
||||
|
||||
หลังทำเสร็จ สรุป:
|
||||
|
||||
1. Files Added
|
||||
2. Files Modified
|
||||
3. Storage Provider Added
|
||||
4. Artifact Schema Added
|
||||
5. Approved PDF Refactor
|
||||
6. Secure Download Added
|
||||
7. UI Updated
|
||||
8. Permissions Added
|
||||
9. Audit Integration
|
||||
10. Compatibility Notes
|
||||
11. Remaining Risks
|
||||
12. Task J Readiness
|
||||
|
||||
---
|
||||
|
||||
# Definition of Done
|
||||
|
||||
Task I ผ่านเมื่อ:
|
||||
|
||||
✔ storage provider interface พร้อม
|
||||
|
||||
✔ local provider พร้อม
|
||||
|
||||
✔ s3/minio provider skeleton พร้อม
|
||||
|
||||
✔ approved PDF ไม่เขียนลง public/generated เป็น source of truth แล้ว
|
||||
|
||||
✔ approved artifact ถูกบันทึกใน crm_document_artifacts
|
||||
|
||||
✔ approved artifact locked/immutable
|
||||
|
||||
✔ approved PDF download ผ่าน secure route
|
||||
|
||||
✔ permission guard download/generate ครบ
|
||||
|
||||
✔ checksum ถูกบันทึก
|
||||
|
||||
✔ quotation UI แสดง artifact metadata
|
||||
|
||||
✔ legacy approvedPdfUrl compatibility มี
|
||||
|
||||
✔ technical debt / ADR updated
|
||||
|
||||
✔ ยังไม่ทำ report/dashboard/notification
|
||||
Reference in New Issue
Block a user