# 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
