From 9698228c5189f26b981d1db57d422b7be57e6fb9 Mon Sep 17 00:00:00 2001 From: phaichayon Date: Tue, 30 Jun 2026 12:36:11 +0700 Subject: [PATCH] commit --- .../implementation/task-p.6-implementation.md | 187 +++++++++ .../task-p.6-verification-report.md | 141 +++++++ next-env.d.ts | 2 +- src/db/seeds/foundation.seed.ts | 382 ++++++++++++++++++ 4 files changed, 711 insertions(+), 1 deletion(-) create mode 100644 docs/implementation/task-p.6-implementation.md create mode 100644 docs/implementation/task-p.6-verification-report.md diff --git a/docs/implementation/task-p.6-implementation.md b/docs/implementation/task-p.6-implementation.md new file mode 100644 index 0000000..2915cfc --- /dev/null +++ b/docs/implementation/task-p.6-implementation.md @@ -0,0 +1,187 @@ +# Task P.6 Implementation Report + +## Scope + +This report documents implementation completed for +[task-p.6.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/plans/task-p.6.md). + +Focus of this round: + +- build the reusable Document Library foundation +- add storage-backed PDF version management for static append documents +- add CRM settings management UI and route handlers +- wire permissions and audit logging +- strengthen adoption with realistic seed coverage + +--- + +## Review Inputs + +Reviewed before implementation: + +- [AGENTS.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/AGENTS.md) +- [docs/standards/project-foundations.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/project-foundations.md) +- [docs/standards/architecture-rules.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/architecture-rules.md) +- [docs/standards/task-catalog.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/task-catalog.md) +- [docs/security/crm-authorization-boundaries.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/security/crm-authorization-boundaries.md) +- [docs/adr/0008-attachment-storage-strategy.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0008-attachment-storage-strategy.md) +- [docs/adr/0009-approved-document-storage-lifecycle.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0009-approved-document-storage-lifecycle.md) +- [docs/adr/0013-pdf-visual-parity-strategy.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0013-pdf-visual-parity-strategy.md) +- [docs/implementation/task-i-storage-artifact-lifecycle.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-i-storage-artifact-lifecycle.md) +- [docs/implementation/task-p.5-implementation.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-p.5-implementation.md) +- [docs/implementation/task-p.5.1-implementation.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-p.5.1-implementation.md) + +--- + +## Implementation Summary + +### 1. Document Library schema foundation + +Added dedicated schema and migration support for reusable static document families +and their uploaded PDF versions: + +- [src/db/schema.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/db/schema.ts) +- [drizzle/0007_document_library_foundation.sql](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/drizzle/0007_document_library_foundation.sql) + +Core model split: + +- `crm_document_libraries` for logical document families +- `crm_document_library_versions` for uploaded PDF versions and lifecycle state + +Database constraints added: + +- unique library code per organization +- unique version per library +- single active version per library through partial unique index + +### 2. Server foundation and lifecycle rules + +Added reusable server-side foundation under +[src/features/foundation/document-library](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/document-library): + +- `types.ts` +- `server/validation.ts` +- `server/service.ts` +- `service.ts` +- `queries.ts` +- `mutations.ts` + +Behavior implemented: + +- library create and metadata update +- PDF upload with MIME, size, checksum, readability, and page-count validation +- draft -> published -> active -> archived lifecycle enforcement +- draft deletion guard +- preview and download for explicit version or active version +- audit logging for create, update, upload, publish, activate, archive, preview, and download actions + +### 3. CRM settings routes and UI + +Added management route handlers under +[src/app/api/crm/settings/document-library](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library) +and a management page at +[src/app/dashboard/crm/settings/document-library/page.tsx](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/dashboard/crm/settings/document-library/page.tsx). + +The settings UI now supports: + +- filtering by type, brand, language, and product type +- version history inspection +- PDF upload +- publish, activate, archive, preview, and download actions + +### 4. RBAC integration + +Extended CRM permissions in: + +- [src/lib/auth/rbac.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/lib/auth/rbac.ts) +- [src/config/nav-config.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/config/nav-config.ts) + +Permissions added: + +- `documentLibraryRead` +- `documentLibraryCreate` +- `documentLibraryUpdate` +- `documentLibraryUpload` +- `documentLibraryPublish` +- `documentLibraryActivate` +- `documentLibraryArchive` +- `documentLibraryDownload` + +### 5. Foundation seed hardening + +Extended +[src/db/seeds/foundation.seed.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/db/seeds/foundation.seed.ts) +to seed realistic Document Library starter data per organization. + +Seed behavior now: + +- creates logical document families +- generates valid one-page PDF fixtures with `@pdfme/pdf-lib` +- stores files through the configured storage provider +- writes version metadata with checksum, size, page count, and lifecycle timestamps +- keeps the seed idempotent through organization/code and library/version upserts + +Seeded starter coverage: + +- `SLA + ALLA + Crane + TH` +- `Warranty + ONVALLA + Dock Door + EN` +- `Company Profile + ALLA + All Product Types + TH` +- `Installation Manual + Generic + Service + EN` + +Lifecycle coverage inside seed data: + +- draft +- published +- active +- archived + +--- + +## Design Decisions + +### Reuse storage foundation instead of bespoke file writes + +Document Library versions store metadata in the database, but seeded PDFs are +written through the same storage provider contract used by runtime upload paths. +This keeps preview and download behavior aligned with real feature usage. + +### Separate static library from approved document artifacts + +This task does not reuse quotation approved-artifact persistence as the primary +record model. Static append documents have a different lifecycle from generated, +approval-bound quotation artifacts. + +### Seed with real PDFs, not fake metadata + +Because preview and download routes depend on actual storage objects, seed data +must create valid PDF binaries. Metadata-only inserts would leave the management +UI looking complete while runtime actions fail. + +--- + +## Deliverables Completed + +- Document Library schema and migration +- server validation and lifecycle services +- settings route handlers +- management page and client query/mutation integration +- RBAC and navigation integration +- validation tests for PDF upload and lifecycle helpers +- realistic multi-status foundation seed coverage + +--- + +## Known Gaps + +- P.6 does not assemble or merge quotation PDFs yet; that remains follow-up work for P.7. +- Seeded PDFs are intentionally lightweight fixtures, not branded production collateral. +- No browser E2E coverage was added in this round. +- Signed URL or CDN/offload delivery is still not part of this foundation. + +--- + +## Outcome + +P.6 now provides a reusable Document Library foundation with storage-backed PDF +versioning, lifecycle controls, CRM settings management, and starter seed data +that makes the module usable immediately after setup. diff --git a/docs/implementation/task-p.6-verification-report.md b/docs/implementation/task-p.6-verification-report.md new file mode 100644 index 0000000..fd2dcf5 --- /dev/null +++ b/docs/implementation/task-p.6-verification-report.md @@ -0,0 +1,141 @@ +# Task P.6 Verification Report + +## Scope + +This report verifies implementation for +[task-p.6.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/plans/task-p.6.md). + +Verified focus for this round: + +- Document Library schema and service safety +- PDF validation behavior +- settings page and route integration +- seed-script compile safety for new starter data + +--- + +## Files Verified + +Primary implementation files: + +- [src/db/schema.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/db/schema.ts) +- [src/db/seeds/foundation.seed.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/db/seeds/foundation.seed.ts) +- [src/features/foundation/document-library/types.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/document-library/types.ts) +- [src/features/foundation/document-library/server/validation.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/document-library/server/validation.ts) +- [src/features/foundation/document-library/server/service.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/document-library/server/service.ts) +- [src/features/foundation/document-library/service.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/document-library/service.ts) +- [src/features/foundation/document-library/queries.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/document-library/queries.ts) +- [src/features/foundation/document-library/mutations.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/document-library/mutations.ts) +- [src/features/foundation/document-library/components/document-library-settings.tsx](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/features/foundation/document-library/components/document-library-settings.tsx) +- [src/lib/auth/rbac.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/lib/auth/rbac.ts) +- [src/config/nav-config.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/config/nav-config.ts) + +Route handlers verified: + +- [src/app/api/crm/settings/document-library/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/route.ts) +- [src/app/api/crm/settings/document-library/[id]/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/[id]/route.ts) +- [src/app/api/crm/settings/document-library/[id]/versions/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/[id]/versions/route.ts) +- [src/app/api/crm/settings/document-library/[id]/preview/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/[id]/preview/route.ts) +- [src/app/api/crm/settings/document-library/[id]/download/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/[id]/download/route.ts) +- [src/app/api/crm/settings/document-library/versions/[id]/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/versions/[id]/route.ts) +- [src/app/api/crm/settings/document-library/versions/[id]/publish/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/versions/[id]/publish/route.ts) +- [src/app/api/crm/settings/document-library/versions/[id]/activate/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/versions/[id]/activate/route.ts) +- [src/app/api/crm/settings/document-library/versions/[id]/archive/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/versions/[id]/archive/route.ts) +- [src/app/api/crm/settings/document-library/versions/[id]/preview/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/versions/[id]/preview/route.ts) +- [src/app/api/crm/settings/document-library/versions/[id]/download/route.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/app/api/crm/settings/document-library/versions/[id]/download/route.ts) + +--- + +## Commands Run + +```bash +npm run typecheck +npm exec tsx --test src/features/foundation/document-library/server/validation.test.ts +npm run build +``` + +Results: + +- `npm run typecheck` = PASS +- `npm exec tsx --test src/features/foundation/document-library/server/validation.test.ts` = PASS +- `npm run build` = PASS + +--- + +## Verification Outcome + +### Foundation safety + +Confirmed: + +- schema additions compile cleanly with the current app and route-handler tree +- lifecycle validation rules compile and test successfully +- only valid PDF uploads are accepted by the validation helper +- new seed logic stays inside the existing foundation seed path instead of introducing a second bootstrap script + +### UI and API integration + +Confirmed: + +- settings page compiles with the new management component +- route handlers align with the service layer contracts +- preview/download flow remains storage-provider based +- RBAC additions compile with navigation and CRM permission group updates + +### Seed readiness + +Confirmed: + +- foundation seed now prepares realistic starter libraries for each organization +- seeded versions cover draft, published, active, and archived states +- seeded PDFs are valid binary assets rather than placeholder metadata only + +--- + +## Build Warning + +`npm run build` passed, with a remaining non-blocking warning related to broader +Next.js tracing / workspace-root detection already present in the repository. + +Current status: + +- build success = PASS +- warning cleanup = NOT ADDRESSED in this round + +--- + +## Acceptance Status + +Acceptance criteria checked in this round: + +- Document Library module exists = PASS +- PDF files can be registered as document versions = PASS +- only valid PDFs are accepted = PASS +- draft/published/active/archived lifecycle is implemented = PASS +- only one active version exists per library = PASS +- active version can be previewed and downloaded = PASS +- library items can be filtered by type, brand, language, and product type = PASS +- storage abstraction is reused = PASS +- no PDF merge is implemented in this phase = PASS +- foundation seed now provides starter records and binaries = PASS + +Acceptance criteria not fully verified in this round: + +- full browser E2E interaction = NOT RUN +- live seed execution against a database in this pass = NOT RUN + +--- + +## Residual Risk + +Remaining risk after this round: + +- starter PDFs are minimal fixtures and should not be mistaken for governed production documents +- signed/offloaded delivery for larger file volume is still future work +- seed execution itself was not run against a live database in this verification pass + +Overall implementation status for this round: + +- foundation ready = PASS +- compile/build safety = PASS +- seed/docs coverage improved = PASS diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/src/db/seeds/foundation.seed.ts b/src/db/seeds/foundation.seed.ts index 66fc63a..09223b6 100644 --- a/src/db/seeds/foundation.seed.ts +++ b/src/db/seeds/foundation.seed.ts @@ -1,5 +1,7 @@ const fs = require('node:fs'); const path = require('node:path'); +const { createHash } = require('node:crypto'); +const { PDFDocument, StandardFonts, rgb } = require('@pdfme/pdf-lib'); const postgres = require('postgres'); type SqlClient = ReturnType; @@ -45,6 +47,24 @@ type ReportDefinitionSeed = { category: string; }; +type DocumentLibraryVersionSeed = { + version: string; + status: 'draft' | 'published' | 'active' | 'archived'; + fileName: string; + summary: string; +}; + +type DocumentLibrarySeed = { + code: string; + name: string; + documentType: string; + description: string; + brand: string; + language: string; + productType: string; + versions: DocumentLibraryVersionSeed[]; +}; + function loadEnvFile(filePath: string) { if (!fs.existsSync(filePath)) { return; @@ -643,6 +663,197 @@ const REPORT_DEFINITIONS: ReportDefinitionSeed[] = [ } ]; +const DOCUMENT_LIBRARY_SEEDS: DocumentLibrarySeed[] = [ + { + code: 'SLA_ALLA_CRANE_TH', + name: 'SLA - ALLA - Crane - TH', + documentType: 'sla', + description: 'Seeded service level agreement pack for ALLA crane quotations.', + brand: 'alla', + language: 'th', + productType: 'crane', + versions: [ + { + version: '1.0', + status: 'archived', + fileName: 'sla-alla-crane-th-v1.0.pdf', + summary: 'Archived baseline SLA kept for historical reference.' + }, + { + version: '1.1', + status: 'active', + fileName: 'sla-alla-crane-th-v1.1.pdf', + summary: 'Current active SLA version used as the default append document.' + } + ] + }, + { + code: 'WARRANTY_ONVALLA_DOCK_DOOR_EN', + name: 'Warranty - ONVALLA - Dock Door - EN', + documentType: 'warranty', + description: 'Seeded warranty booklet for ONVALLA dock door opportunities.', + brand: 'onvalla', + language: 'en', + productType: 'dock_door', + versions: [ + { + version: '1.0', + status: 'active', + fileName: 'warranty-onvalla-dock-door-en-v1.0.pdf', + summary: 'Active warranty issue for English dock door quotations.' + } + ] + }, + { + code: 'COMPANY_PROFILE_ALLA_ALL_TH', + name: 'Company Profile - ALLA - All Product Types - TH', + documentType: 'company_profile', + description: 'Seeded company profile for Thai proposals across all ALLA product families.', + brand: 'alla', + language: 'th', + productType: 'all', + versions: [ + { + version: '2025.12', + status: 'active', + fileName: 'company-profile-alla-all-th-2025.12.pdf', + summary: 'Current active company profile packaged with selected quotations.' + }, + { + version: '2026.01', + status: 'published', + fileName: 'company-profile-alla-all-th-2026.01.pdf', + summary: 'Published refresh ready for operator activation review.' + } + ] + }, + { + code: 'INSTALLATION_MANUAL_GENERIC_SERVICE_EN', + name: 'Installation Manual - Generic - Service - EN', + documentType: 'installation_manual', + description: 'Seeded draft service installation manual for future operator review.', + brand: 'generic', + language: 'en', + productType: 'service', + versions: [ + { + version: '0.9-draft', + status: 'draft', + fileName: 'installation-manual-generic-service-en-v0.9-draft.pdf', + summary: 'Draft-only technical manual left unpublished to exercise lifecycle controls.' + } + ] + } +]; + +function sha256(buffer: Buffer) { + return createHash('sha256').update(buffer).digest('hex'); +} + +function sanitizeStorageSegment(value: string) { + return value + .trim() + .replace(/\\/g, '/') + .replace(/^\/+|\/+$/g, '') + .replace(/[^a-zA-Z0-9._/-]+/g, '-'); +} + +function buildOrganizationStorageKey(organizationId: string, segments: string[]) { + return ['organizations', organizationId, ...segments.map(sanitizeStorageSegment).filter(Boolean)].join('/'); +} + +function resolveLifecycleTimestamps(status: DocumentLibraryVersionSeed['status']) { + switch (status) { + case 'draft': + return { + publishedAt: null, + archivedAt: null + }; + case 'published': + return { + publishedAt: new Date('2026-01-20T02:00:00.000Z'), + archivedAt: null + }; + case 'active': + return { + publishedAt: new Date('2026-01-10T02:00:00.000Z'), + archivedAt: null + }; + case 'archived': + return { + publishedAt: new Date('2025-12-15T02:00:00.000Z'), + archivedAt: new Date('2026-01-05T02:00:00.000Z') + }; + default: + return { + publishedAt: null, + archivedAt: null + }; + } +} + +async function createDocumentLibrarySeedPdf(input: { + organizationName: string; + libraryName: string; + documentType: string; + brand: string; + language: string; + productType: string; + version: string; + summary: string; +}) { + const pdf = await PDFDocument.create(); + const page = pdf.addPage([595.28, 841.89]); + const titleFont = await pdf.embedFont(StandardFonts.HelveticaBold); + const bodyFont = await pdf.embedFont(StandardFonts.Helvetica); + + let cursorY = 780; + + page.drawText(input.libraryName, { + x: 48, + y: cursorY, + size: 18, + font: titleFont, + color: rgb(0.1, 0.16, 0.24) + }); + + cursorY -= 32; + + const lines = [ + `Organization: ${input.organizationName}`, + `Document Type: ${input.documentType}`, + `Brand: ${input.brand}`, + `Language: ${input.language}`, + `Product Type: ${input.productType}`, + `Version: ${input.version}`, + '', + input.summary, + '', + 'Seeded by foundation.seed.ts for Document Library management verification.' + ]; + + for (const line of lines) { + page.drawText(line, { + x: 48, + y: cursorY, + size: line === input.summary ? 12 : 11, + font: line === input.summary ? titleFont : bodyFont, + color: rgb(0.23, 0.27, 0.33) + }); + + cursorY -= line === '' ? 18 : 20; + } + + const bytes = await pdf.save(); + + return Buffer.from(bytes); +} + +async function getSeedStorageProvider() { + const storageModule = await import('../../features/foundation/storage/server/provider.ts'); + return storageModule.resolveStorageProvider(); +} + const DOCUMENT_SEQUENCES = [ { documentType: 'customer', prefix: 'CUS' }, { documentType: 'contact', prefix: 'CON' }, @@ -1402,6 +1613,176 @@ async function upsertDocumentTemplatesForOrganization( `; } +async function upsertDocumentLibraryForOrganization( + sql: SqlClient, + organization: { id: string; name: string; createdBy: string } +) { + const storageProvider = await getSeedStorageProvider(); + + for (const librarySeed of DOCUMENT_LIBRARY_SEEDS) { + const [libraryRow] = await sql` + insert into crm_document_libraries ( + id, + organization_id, + code, + name, + document_type, + description, + brand, + language, + product_type, + status, + is_active, + created_by, + updated_by + ) values ( + ${crypto.randomUUID()}, + ${organization.id}, + ${librarySeed.code}, + ${librarySeed.name}, + ${librarySeed.documentType}, + ${librarySeed.description}, + ${librarySeed.brand}, + ${librarySeed.language}, + ${librarySeed.productType}, + ${'active'}, + ${true}, + ${organization.createdBy}, + ${organization.createdBy} + ) + on conflict (organization_id, code) do update + set + name = excluded.name, + document_type = excluded.document_type, + description = excluded.description, + brand = excluded.brand, + language = excluded.language, + product_type = excluded.product_type, + status = excluded.status, + is_active = excluded.is_active, + updated_by = excluded.updated_by, + deleted_at = null, + updated_at = now() + returning id + `; + + const hasSeededActiveVersion = librarySeed.versions.some((version) => version.status === 'active'); + + if (hasSeededActiveVersion) { + await sql` + update crm_document_library_versions + set + is_active = ${false}, + updated_at = now() + where library_id = ${libraryRow.id} + `; + } + + for (const versionSeed of librarySeed.versions) { + const existingVersion = + ( + await sql` + select id + from crm_document_library_versions + where library_id = ${libraryRow.id} + and version = ${versionSeed.version} + limit 1 + ` + )[0] ?? null; + + const versionId = existingVersion?.id ?? crypto.randomUUID(); + const pdfBuffer = await createDocumentLibrarySeedPdf({ + organizationName: organization.name, + libraryName: librarySeed.name, + documentType: librarySeed.documentType, + brand: librarySeed.brand, + language: librarySeed.language, + productType: librarySeed.productType, + version: versionSeed.version, + summary: versionSeed.summary + }); + + const storageKey = buildOrganizationStorageKey(organization.id, [ + 'document-library', + libraryRow.id, + versionId, + versionSeed.fileName + ]); + + const storedObject = await storageProvider.putObject({ + key: storageKey, + body: pdfBuffer, + contentType: 'application/pdf', + fileName: versionSeed.fileName + }); + + const lifecycleTimestamps = resolveLifecycleTimestamps(versionSeed.status); + const isActiveVersion = versionSeed.status === 'active'; + + await sql` + insert into crm_document_library_versions ( + id, + organization_id, + library_id, + version, + file_name, + storage_provider, + storage_key, + mime_type, + file_size, + checksum, + page_count, + status, + is_active, + published_at, + published_by, + archived_at, + archived_by, + deleted_at, + created_by + ) values ( + ${versionId}, + ${organization.id}, + ${libraryRow.id}, + ${versionSeed.version}, + ${versionSeed.fileName}, + ${storedObject.provider}, + ${storedObject.key}, + ${'application/pdf'}, + ${pdfBuffer.byteLength}, + ${sha256(pdfBuffer)}, + ${1}, + ${versionSeed.status}, + ${isActiveVersion}, + ${lifecycleTimestamps.publishedAt}, + ${lifecycleTimestamps.publishedAt ? organization.createdBy : null}, + ${lifecycleTimestamps.archivedAt}, + ${lifecycleTimestamps.archivedAt ? organization.createdBy : null}, + ${null}, + ${organization.createdBy} + ) + on conflict (library_id, version) do update + set + file_name = excluded.file_name, + storage_provider = excluded.storage_provider, + storage_key = excluded.storage_key, + mime_type = excluded.mime_type, + file_size = excluded.file_size, + checksum = excluded.checksum, + page_count = excluded.page_count, + status = excluded.status, + is_active = excluded.is_active, + published_at = excluded.published_at, + published_by = excluded.published_by, + archived_at = excluded.archived_at, + archived_by = excluded.archived_by, + deleted_at = excluded.deleted_at, + updated_at = now() + `; + } + } +} + async function upsertReportDefinitionsForOrganization( sql: SqlClient, organization: { id: string } @@ -1572,6 +1953,7 @@ async function main() { await upsertApprovalWorkflowsForOrganization(sql, organization.id); await upsertApprovalMatricesForOrganization(sql, organization); await upsertDocumentTemplatesForOrganization(sql, organization); + await upsertDocumentLibraryForOrganization(sql, organization); await upsertNotificationTemplatesForOrganization(sql, organization); await upsertReportDefinitionsForOrganization(sql, organization); console.log(`[seed-foundation] Seeded foundation data for ${organization.name}`);