8.4 KiB
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
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 มากขึ้น
จากเดิม:
public/generated/quotations/<organizationId>/
ไปเป็น:
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:
src/features/foundation/storage/**
Files:
types.ts
service.ts
server/local-provider.ts
server/s3-provider.ts
server/provider.ts
Interface:
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:
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:
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 ถ้ายังไม่มี:
crm_document_artifacts
Fields:
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:
preview
approved_pdf
export
status:
active
locked
voided
deleted
Rules:
- approved_pdf ต้อง lock หลัง generate
- approved_pdf ห้าม overwrite
- ถ้าต้อง regenerate ให้สร้าง artifact ใหม่หรือ void artifact เดิมก่อนตาม policy
- organizationId required
Scope I4: Checksum
เพิ่ม utility:
src/features/foundation/storage/server/checksum.ts
รองรับ:
sha256(buffer)
ใช้บันทึก checksum ของ PDF artifact
Scope I5: Approved PDF Storage Refactor
ปรับ generateApprovedQuotationPdf() จากการเขียน:
public/generated/...
เป็น:
StorageProvider.putObject()
crm_document_artifacts insert
quotation.approvedPdfUrl update เป็น secure reference
Recommended approvedPdfUrl:
artifact:<artifactId>
หรือถ้ามี field ใหม่:
approvedArtifactId
ถ้าต้องเพิ่ม field ใน quotation:
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
ปรับ:
GET /api/crm/quotations/[id]/approved-pdf
ให้:
- อ่าน artifact จาก database
- validate organizationId
- validate permission
- ดึงไฟล์ผ่าน StorageProvider.getObject()
- stream PDF กลับ
- ไม่ใช้ public file path ตรง ๆ
เพิ่ม route generic ได้ถ้าเหมาะสม:
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:
src/features/foundation/document-artifact/server/service.ts
Functions:
createArtifact()
getArtifact()
getActiveArtifactForEntity()
lockArtifact()
voidArtifact()
deleteArtifactMetadataOnly()
downloadArtifact()
Rules:
lockedartifact ห้ามแก้ storageKey/fileSize/checksumvoidedartifact ยังเก็บ 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 เดิมแบบ:
/generated/...
ให้ไม่ลบทิ้งทันที
เพิ่ม compatibility:
- ถ้า approvedPdfUrl เป็น artifact reference → ใช้ artifact
- ถ้าเป็น legacy local path → แสดง legacy warning
- future migration script ค่อย migrate legacy file
Scope I10: Documentation
Update:
docs/implementation/technical-debt.md
docs/adr/0009-approved-document-storage-lifecycle.md
เพิ่ม:
Storage provider abstraction
Local vs S3/MinIO policy
Immutable artifact policy
Legacy public/generated migration
Download security model
Permissions
เพิ่มถ้ายังไม่มี:
crm.document_artifact.read
crm.document_artifact.download
crm.document_artifact.void
Audit
Audit required:
crm_document_artifact
Actions:
create
lock
void
download
ถ้า download audit ทำให้ระบบหนัก ให้ทำ optional toggle ได้
ห้ามทำ
Report
Dashboard KPI
Notification
Template Designer
Mapping Editor
Full object-storage admin UI
Bulk migration of legacy files
Output
หลังทำเสร็จ สรุป:
- Files Added
- Files Modified
- Storage Provider Added
- Artifact Schema Added
- Approved PDF Refactor
- Secure Download Added
- UI Updated
- Permissions Added
- Audit Integration
- Compatibility Notes
- Remaining Risks
- 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