# 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// ``` ไปเป็น: ```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 getObject(input: GetObjectInput): Promise deleteObject(input: DeleteObjectInput): Promise objectExists(input: ObjectExistsInput): Promise getSignedUrl?(input: GetSignedUrlInput): Promise } ``` 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: ``` หรือถ้ามี 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