476 lines
8.4 KiB
Markdown
476 lines
8.4 KiB
Markdown
# 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
|