This commit is contained in:
phaichayon
2026-06-16 17:01:29 +07:00
parent 90ee59d388
commit 0a484e0b45
28 changed files with 6483 additions and 277 deletions

View File

@@ -3,22 +3,30 @@ accepted
Context:
Task H introduced server-side PDF generation for quotation preview, download, and approved-document persistence.
Task H.1 validated the full path with a real quotation fixture and confirmed that approved artifacts currently persist to local disk under `public/generated/...`.
Task H.1 validated the full path with a real quotation fixture and confirmed that approved artifacts originally persisted to local disk under `public/generated/...`.
Task I introduces a storage-provider abstraction and a database-backed document artifact model so approved PDFs are no longer tied to raw public paths as the source of truth.
Decision:
- Keep approved quotation PDFs on local filesystem storage for development and MVP use.
- Persist the generated file path on the quotation row via `approved_pdf_url`.
- Persist `approved_snapshot` and `approved_template_version_id` alongside the stored file path so the approved artifact can be traced back to the data and template used at generation time.
- Treat this storage model as acceptable only for local/dev and early MVP deployment, not as the final production storage architecture.
- Use a storage provider abstraction for approved quotation PDFs.
- Support a local provider for development and an S3 / MinIO-compatible provider for production-oriented deployments.
- Persist approved-document metadata in `crm_document_artifacts`.
- Persist `approved_artifact_id` on the quotation row as the primary approved-artifact reference.
- Keep `approved_pdf_url` as a compatibility field and allow `artifact:<artifactId>` references during the migration period.
- Lock approved PDF artifacts after generation and treat them as immutable-first records.
Consequences:
- Preview/download/generate-approved flows can ship now without blocking on object storage integration.
- Approved PDF persistence works with the current dashboard and static file serving model.
- Local public storage is simple, but it does not provide immutability guarantees, signed access control, retention policy, or durable storage semantics expected for long-lived production artifacts.
- Approved PDF persistence no longer depends on raw `public/generated/...` paths as the system of record.
- Secure download routes can resolve artifacts from database metadata and storage-provider reads.
- Local storage remains acceptable for development, but production-oriented deployments can move to S3/MinIO-compatible object storage without changing the quotation flow contract.
- Legacy approved PDFs stored under `public/generated/...` still require explicit migration and remain a temporary compatibility burden.
Future:
- introduce an object-storage abstraction shared by generated documents and manual attachments
- migrate legacy approved PDFs into `crm_document_artifacts`
- extend the storage abstraction to manual attachments as well as generated artifacts
- support signed URLs or another private delivery strategy where required
- define immutable approved artifact rules and whether regeneration is allowed
- add retention/cleanup policy for superseded or regenerated artifacts
- define operator-facing void/replacement workflow for immutable approved artifacts
- add retention/cleanup policy for superseded or voided artifacts
- move approved-PDF generation to an async job when post-approval auto-generation is introduced

View File

@@ -0,0 +1,144 @@
# Task D.1: Enquiry Assignment and CRM Role Stabilization
## 1. Files Added
- `src/app/api/crm/enquiries/[id]/_schemas.ts`
- `src/app/api/crm/enquiries/[id]/assign/route.ts`
- `src/app/api/crm/enquiries/[id]/reassign/route.ts`
- `src/features/crm/enquiries/components/enquiry-assignment-dialog.tsx`
- `drizzle/0008_clean_justin_hammer.sql`
- `drizzle/meta/0008_snapshot.json`
## 2. Files Modified
- `src/db/schema.ts`
- `src/lib/auth/rbac.ts`
- `src/app/dashboard/crm/enquiries/page.tsx`
- `src/app/dashboard/crm/enquiries/[id]/page.tsx`
- `src/features/crm/enquiries/api/types.ts`
- `src/features/crm/enquiries/api/service.ts`
- `src/features/crm/enquiries/api/mutations.ts`
- `src/features/crm/enquiries/server/service.ts`
- `src/features/crm/enquiries/components/enquiry-columns.tsx`
- `src/features/crm/enquiries/components/enquiries-table.tsx`
- `src/features/crm/enquiries/components/enquiry-listing.tsx`
- `src/features/crm/enquiries/components/enquiry-detail.tsx`
- `src/features/crm/enquiries/components/enquiry-cell-action.tsx`
- `src/features/users/api/types.ts`
- `src/features/users/schemas/user.ts`
- `src/features/users/components/user-form-sheet.tsx`
- `src/app/api/users/_lib.ts`
- `src/app/api/users/route.ts`
- `src/app/api/organizations/route.ts`
- `src/features/foundation/auth-context/service.ts`
## 3. Schema Added
Added assignment fields to `crm_enquiries`:
- `assigned_to_user_id`
- `assigned_at`
- `assigned_by`
- `assignment_remark`
Rules implemented:
- assignee must belong to the same organization
- assigner is the current user
- latest assignment timestamp is persisted
- assignment remark is optional
## 4. API Routes Added
- `POST /api/crm/enquiries/[id]/assign`
- `POST /api/crm/enquiries/[id]/reassign`
Payload:
```ts
{
assignedToUserId: string;
remark?: string;
}
```
Behavior:
- requires organization access
- enforces `crm.enquiry.assign` or `crm.enquiry.reassign`
- validates assignee organization membership
- rejects assign on already-assigned enquiry
- rejects reassign on unassigned enquiry
## 5. Permissions Added
- `crm.enquiry.assign`
- `crm.enquiry.reassign`
CRM business-role set was also stabilized around:
- `sales_manager`
- `sales`
- `sales_support`
- `department_manager`
- `top_manager`
Legacy IT-CENTER business roles were removed from active runtime paths and defaults.
## 6. UI Added
Enquiry detail now supports:
- `Assign Sales`
- `Reassign Sales`
- assigned sales metadata display
Enquiry list now shows:
- `Assigned Sales` column
Assignment dialog now includes:
- `Sales User`
- `Remark`
The sales-user dropdown is populated from organization memberships instead of hardcoded values.
## 7. Audit Integration
Assignment writes audit logs under:
- `entityType = crm_enquiry`
Actions added:
- `assign`
- `reassign`
Audit payload shape:
- `beforeData.oldAssignedToUserId`
- `afterData.newAssignedToUserId`
- `afterData.remark`
## 8. Membership / Permission Stabilization
Workspace membership management now uses CRM-aligned business roles instead of the old template roles.
Updated areas:
- user API types and validation
- membership defaults
- organization-management fallbacks
- auth-context business-role fallback
## 9. Remaining Risks
- current `sales` access still operates at organization scope; there is not yet a server-enforced “own or assigned only” data policy
- old database rows that still carry removed legacy business-role strings may need a one-time data backfill if they exist in a real environment
- assignable-user filtering currently accepts organization admins plus sales-oriented roles; if future CRM staffing rules become stricter, that policy should move into a dedicated assignment rule layer
## 10. Verification
- `npx tsc --noEmit`
- `npx oxlint` on changed Task D.1 files

View File

@@ -0,0 +1,122 @@
# Task H.1: PDF Generation Stabilization
## 1. Files Added
- `scripts/seed-task-h1-fixture.js`
- `scripts/verify-task-h1.js`
- `docs/adr/0009-approved-document-storage-lifecycle.md`
## 2. Files Modified
- `package.json`
- `docs/implementation/technical-debt.md`
Task H.1 builds on the Task H PDF pipeline and focuses on validation, fixture support, documentation, and storage-lifecycle clarification.
## 3. Smoke Test Record Used
Added a dev-only fixture script for a real quotation scenario:
- approved fixture code: `QT-TASK-H1-APPROVED`
- draft fixture code: `QT-TASK-H1-DRAFT`
- customer fixture code: `CUS-TASK-H1`
Fixture script provisions:
- organization-scoped customer and contact
- approved quotation with item, customer role, scope/exclusion/payment topics, and completed approval history
- draft quotation for negative approved-PDF validation
- test users for permission checks
## 4. PDF APIs Verified
Task H.1 verification script covers:
- `GET /api/crm/quotations/[id]/pdf-preview`
- `GET /api/crm/quotations/[id]/pdf-download`
- `POST /api/crm/quotations/[id]/approved-pdf`
- `GET /api/crm/quotations/[id]/approved-pdf`
Expected checks included:
- preview responds as inline PDF
- download responds as attachment PDF
- approved-PDF POST succeeds for approved quotation
- approved-PDF GET returns persisted artifact
- non-approved quotation cannot generate approved PDF
## 5. Persistence Verified
Task H.1 verification targets:
- `approvedPdfUrl`
- `approvedSnapshot`
- `approvedTemplateVersionId`
- quotation attachment metadata row
- audit log row
- generated file under `public/generated/quotations/<organizationId>/`
## 6. Permission Verified
Fixture and verification support explicit checks for:
- `super_admin`
- admin workspace member
- regular user with PDF permissions
- regular user without PDF permissions
Permissions covered:
- `crm.quotation.pdf.preview`
- `crm.quotation.pdf.download`
- `crm.quotation.pdf.generate_approved`
## 7. Error Handling / Hardening
Task H.1 centered hardening around safe operational verification rather than a large refactor.
The main stabilization additions were:
- reusable dev fixture creation
- repeatable HTTP verification script
- clearer storage lifecycle documentation
- explicit debt tracking for fonts, local storage, and approved-artifact policy
## 8. ADR / Technical Debt Updated
Added ADR:
- `docs/adr/0009-approved-document-storage-lifecycle.md`
Updated debt notes in:
- `docs/implementation/technical-debt.md`
Tracked concerns include:
- font compatibility fallback
- local filesystem storage limitation
- approved artifact immutability policy
- post-approval automation gap
- missing CI-backed PDF E2E coverage
## 9. Remaining Risks
- approved PDF storage still uses local public filesystem semantics and is not production-grade object storage
- approved artifact lifecycle is still not immutable by policy
- PDF verification is script-based and not yet part of CI
- task fixture scripts currently depend on a prepared local environment and seeded master-option / approval data
## 10. Task I Readiness
Task H.1 leaves the PDF flow better prepared for Task I by providing:
- a repeatable quotation fixture path
- a repeatable verification script
- explicit storage-lifecycle decision documentation
- visible technical-debt boundaries for future production hardening
## 11. Verification Commands
- `npm run seed:task-h1-fixture`
- `npm run verify:task-h1`

View File

@@ -0,0 +1,155 @@
# Task I: Storage Abstraction and Approved Artifact Lifecycle
## 1. Files Added
- `src/features/foundation/storage/types.ts`
- `src/features/foundation/storage/service.ts`
- `src/features/foundation/storage/server/local-provider.ts`
- `src/features/foundation/storage/server/s3-provider.ts`
- `src/features/foundation/storage/server/provider.ts`
- `src/features/foundation/storage/server/checksum.ts`
- `src/features/foundation/document-artifact/server/service.ts`
- `src/app/api/crm/document-artifacts/[id]/download/route.ts`
- `drizzle/0009_lazy_shard.sql`
- `drizzle/meta/0009_snapshot.json`
## 2. Files Modified
- `package.json`
- `package-lock.json`
- `src/db/schema.ts`
- `src/lib/auth/rbac.ts`
- `src/features/foundation/pdf-generator/server/service.ts`
- `src/features/foundation/approval/server/service.ts`
- `src/features/crm/quotations/api/types.ts`
- `src/features/crm/quotations/server/service.ts`
- `src/features/crm/quotations/components/quotation-detail.tsx`
- `src/app/api/crm/quotations/[id]/approved-pdf/route.ts`
- `scripts/seed-task-h1-fixture.js`
- `docs/implementation/technical-debt.md`
- `docs/adr/0009-approved-document-storage-lifecycle.md`
## 3. Storage Provider Added
Added a storage foundation under `src/features/foundation/storage/**`.
Providers:
- local provider for development
- S3 / MinIO-compatible provider for production-oriented storage
Configuration supported:
- `STORAGE_PROVIDER`
- `LOCAL_STORAGE_ROOT`
- `S3_ENDPOINT`
- `S3_REGION`
- `S3_BUCKET`
- `S3_ACCESS_KEY_ID`
- `S3_SECRET_ACCESS_KEY`
- `S3_FORCE_PATH_STYLE`
## 4. Artifact Schema Added
Added `crm_document_artifacts` with:
- organization/entity identity
- document and artifact types
- template version reference
- storage provider and storage key
- file metadata
- checksum
- lifecycle status
- generated / locked / voided metadata
- JSON metadata payload
Added `approved_artifact_id` to `crm_quotations`.
## 5. Approved PDF Refactor
Approved quotation PDFs now:
- write through `StorageProvider.putObject()`
- persist checksum via SHA-256
- create a `crm_document_artifacts` row
- lock the artifact after generation
- update quotation `approvedArtifactId`
- keep `approvedPdfUrl` as compatibility reference in `artifact:<artifactId>` form
Rules enforced:
- only approved quotations can generate approved PDF
- locked approved artifacts cannot be overwritten
- existing active approved artifact blocks silent regeneration
## 6. Secure Download Added
Added secure artifact route:
- `GET /api/crm/document-artifacts/[id]/download`
Updated quotation approved-PDF flow so secure reads resolve artifact metadata from the database and fetch bytes through the storage provider instead of treating public file paths as source of truth.
## 7. UI Updated
Quotation detail now shows approved artifact metadata when available:
- file name
- generated by
- generated at
- checksum short form
- locked status badge
It also shows a legacy warning when a quotation still references old `/generated/...` storage without an artifact record.
## 8. Permissions Added
- `crm.document_artifact.read`
- `crm.document_artifact.download`
- `crm.document_artifact.void`
Defaults were added alongside the existing quotation PDF permissions in RBAC role derivation.
## 9. Audit Integration
Artifact service now audits:
- `create`
- `lock`
- `void`
- `download`
Entity type:
- `crm_document_artifact`
## 10. Compatibility Notes
Compatibility behavior now supports:
- `approvedArtifactId` when present
- `approvedPdfUrl = artifact:<artifactId>` during migration
- legacy `/generated/...` approved PDF paths with warning fallback
Task I intentionally does not bulk-migrate legacy files yet.
## 11. Remaining Risks
- artifact void/regeneration still has no admin UI workflow
- legacy approved PDFs still need one-time migration into artifact storage
- signed-URL delivery is not enabled yet; downloads are still server-streamed
- storage-provider health and bucket policy checks are still operational concerns outside the app
## 12. Task J Readiness
Task I leaves the app ready for:
- richer artifact lifecycle tooling
- legacy-file migration command
- object-storage rollout beyond local development
- future secure delivery strategies such as signed URLs
## 13. Verification
- `npx tsc --noEmit`
- `npm run gen`

View File

@@ -3,136 +3,242 @@
## After Task D
### Database foreign keys
Customer, contact, and enquiry relationships are still enforced mainly in application logic.
Future:
- add FK constraints after the CRM schema settles
- review cascade behavior before enabling hard relational enforcement
### Date input UX
Several forms still use plain `YYYY-MM-DD` text/date inputs.
Future:
- move to a shared date picker pattern
- standardize timezone handling for CRM forms
### Related enquiry pagination
Customer detail can show related enquiries, but that view still has no dedicated pagination strategy.
Future:
- add paging once real data volume grows
### Follow-up activity timeline
Activity history still relies heavily on audit payloads instead of a curated domain timeline.
Future:
- build a combined activity view/service for CRM interactions
## After Task D.1
### Enquiry Own / Assigned Scope
Current:
sales role ยังอ่าน enquiry ในระดับ organization scope
Future:
เพิ่ม server-side policy:
- sales เห็นเฉพาะ enquiry ที่ตัวเองสร้าง
- หรือ enquiry ที่ assigned ให้ตัวเอง
- sales_manager เห็นทั้งทีม
Priority:
High
### Legacy Business Role Backfill
Current:
ระบบ runtime ใช้ CRM businessRole แล้ว แต่ DB เก่าอาจยังมี role จาก IT-CENTER
Future:
ทำ one-time migration/backfill script สำหรับ memberships.businessRole
Priority:
Medium
## After Task E
### Mixed tax rollup
Quotation items support per-line tax input, but summary calculation still assumes a header-centric tax model.
Future:
- support mixed-tax aggregation from actual item rows
- align document totals and reporting with per-line tax logic
### Attachment storage abstraction
Quotation attachments started as metadata-only and approved PDFs now write to local disk, but there is still no real storage abstraction.
Future:
- introduce a provider abstraction
- support upload/download lifecycle consistently across manual attachments and generated artifacts
- define migration strategy for existing local/public paths
### Revision snapshot coverage
Revision copy currently focuses on quotation header, items, customers, and topics.
Future:
- decide whether follow-ups and attachments belong in revision snapshots
- define immutable vs mutable child-data behavior across revisions
### CRM foreign keys
Core CRM tables still depend on organization-scoped validation in service code.
Future:
- add FK constraints after schema churn slows down
- review delete and soft-delete interactions first
## After Task F
### Permission sync for existing memberships
New permissions are defined in code, but old membership rows may still hold stale permission arrays.
Future:
- add a permission sync/backfill command
- provide admin tooling for role and permission maintenance
### Approval workflow configuration UI
Approval workflows are seeded and usable, but still not configurable from the dashboard.
Future:
- build workflow setup screens
- add step editing and role mapping management
### Approval notifications
Approval flow has no queue-backed notification channel yet.
Future:
- add notification jobs
- support reminders and escalation policy
## After Task G
### Document mapping editor
Template mappings are seeded and readable, but not manageable from UI.
Future:
- add mapping CRUD for admins
- support table column editing
- add draft/publish template workflow
### Product-specific template resolution
Quotation document template resolution still defaults to a coarse product selection strategy.
Future:
- define clearer rules for crane, dock door, solar, and service-specific templates
## After Task H / H.1
### Font compatibility fallback
PDF generation now uses vendored Cordia TTF files from `public/fonts`, which fixed the TTC runtime issue, but generator-side fallback normalization still remains as a safety net.
Future:
- keep validating Thai text metrics against designer output
- remove fallback normalization only after render parity is proven stable
### Local filesystem storage
Approved PDFs currently persist under `public/generated/quotations/<organizationId>/`.
Future:
- replace local public storage for production with object storage
- add signed/private delivery where required
- define backup and cleanup expectations
### Approved artifact immutability
Approved PDF persistence exists, but the lifecycle is not immutable by policy yet.
Future:
- define whether regeneration is allowed after approval
- version approved artifacts explicitly if regeneration must remain possible
- document which snapshot fields are contractual records
### Post-approval automation
Approved PDF generation is still manual by design.
Future:
- add a non-blocking auto-generation hook after final approval
- move heavy generation/retry behavior to a background job when needed
### PDF pipeline test coverage
Task H.1 added dev fixture and HTTP verification scripts, but there is still no CI-backed E2E coverage for the PDF pipeline.
Future:
- promote fixture verification into automated test coverage
- add regression checks for template mappings, storage persistence, and permission enforcement
## After Task I
### Storage provider production rollout
Task I introduced a storage provider abstraction with local and S3/MinIO-compatible implementations, but the production rollout still depends on environment configuration and infrastructure ownership outside the app.
Future:
- validate real bucket policies, credentials rotation, and environment separation
- add operational runbooks for provider misconfiguration and storage outage handling
- add smoke checks for startup-time storage provider health
### Legacy approved PDF migration
Task I keeps compatibility for legacy `public/generated/...` approved PDFs, but those legacy files are still outside the new artifact source-of-truth model.
Future:
- add a one-time migration script from legacy local paths into `crm_document_artifacts`
- backfill `approvedArtifactId` where possible
- remove legacy compatibility once migration is complete and verified
### Artifact lifecycle policy depth
Approved artifacts are now immutable-first and locked after generation, but void/regeneration policy is still service-level rather than a full operator workflow.
Future:
- add explicit admin tooling for artifact void and replacement flows
- define whether artifact voiding also clears or supersedes quotation-facing references automatically
- document retention expectations for voided objects in object storage
### Secure artifact delivery model
Approved-PDF viewing now goes through secure server routes and storage-backed reads, but signed-url/offload behavior is not yet used.
Future:
- decide when direct signed URLs are acceptable versus mandatory server streaming
- add response caching strategy for large artifact downloads
- evaluate optional download-audit sampling if volume grows