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}`);