This commit is contained in:
phaichayon
2026-06-16 10:57:41 +07:00
parent dff22d75b5
commit 357414c247
33 changed files with 9281 additions and 26 deletions

View File

@@ -18,6 +18,24 @@ type BranchSeedRow = {
label: string;
};
type TemplateColumnSeed = {
columnName: string;
sourceField: string;
columnLetter?: string;
sortOrder: number;
formatMask?: string | null;
};
type TemplateMappingSeed = {
placeholderKey: string;
sourcePath: string;
dataType: 'scalar' | 'multiline' | 'table' | 'image';
defaultValue?: string | null;
formatMask?: string | null;
sortOrder: number;
columns?: TemplateColumnSeed[];
};
function loadEnvFile(filePath: string) {
if (!fs.existsSync(filePath)) {
return;
@@ -213,6 +231,145 @@ const APPROVAL_WORKFLOWS = [
}
];
const DOCUMENT_TEMPLATE_MAPPINGS: TemplateMappingSeed[] = [
{
placeholderKey: 'customer_name',
sourcePath: 'customer.name',
dataType: 'scalar',
sortOrder: 1
},
{
placeholderKey: 'customer_addr',
sourcePath: 'customer.address',
dataType: 'multiline',
sortOrder: 2
},
{
placeholderKey: 'customer_tel',
sourcePath: 'customer.phone',
dataType: 'scalar',
sortOrder: 3
},
{
placeholderKey: 'customer_email',
sourcePath: 'customer.email',
dataType: 'scalar',
sortOrder: 4
},
{
placeholderKey: 'customer_att',
sourcePath: 'contact.name',
dataType: 'scalar',
sortOrder: 5
},
{
placeholderKey: 'project_name',
sourcePath: 'quotation.projectName',
dataType: 'scalar',
sortOrder: 6
},
{
placeholderKey: 'site_location',
sourcePath: 'quotation.projectLocation',
dataType: 'multiline',
sortOrder: 7
},
{
placeholderKey: 'quotation_date',
sourcePath: 'quotation.quotationDate',
dataType: 'scalar',
formatMask: 'date',
sortOrder: 8
},
{
placeholderKey: 'quotation_code',
sourcePath: 'quotation.code',
dataType: 'scalar',
sortOrder: 9
},
{
placeholderKey: 'quotation_price',
sourcePath: 'quotation.totalAmount',
dataType: 'scalar',
formatMask: 'currency',
sortOrder: 10
},
{
placeholderKey: 'currency',
sourcePath: 'quotation.currencyCode',
dataType: 'scalar',
sortOrder: 11
},
{
placeholderKey: 'exclusion_data',
sourcePath: 'topics.exclusion',
dataType: 'multiline',
sortOrder: 12
},
{
placeholderKey: 'item_topic',
sourcePath: 'topics.scope',
dataType: 'multiline',
sortOrder: 13
},
{
placeholderKey: 'items_table',
sourcePath: 'items',
dataType: 'table',
sortOrder: 14,
columns: [
{ columnName: 'Item', sourceField: 'itemNumber', columnLetter: 'A', sortOrder: 1 },
{ columnName: 'Description', sourceField: 'description', columnLetter: 'B', sortOrder: 2 },
{ columnName: 'Qty', sourceField: 'quantity', columnLetter: 'C', sortOrder: 3 },
{ columnName: 'Unit', sourceField: 'unitLabel', columnLetter: 'D', sortOrder: 4 },
{
columnName: 'Unit Price',
sourceField: 'unitPrice',
columnLetter: 'E',
sortOrder: 5,
formatMask: 'currency'
},
{
columnName: 'Total',
sourceField: 'totalPrice',
columnLetter: 'F',
sortOrder: 6,
formatMask: 'currency'
}
]
}
];
function loadTemplateSchemaByOrganizationName(organizationName: string) {
const normalized = organizationName.toUpperCase();
const fileName = normalized.includes('ONVALLA')
? 'ONVALLA_template_pdfme_fainal3.json'
: normalized.includes('ALLA')
? 'ALLA_template_pdfme_fainal3.json'
: null;
if (!fileName) {
return {
filePath: null,
templateName: `${organizationName} Quotation Standard`,
schemaJson: {
basePdf: '',
schemas: [],
pdfmeVersion: 'fallback'
}
};
}
const absolutePath = path.resolve(process.cwd(), 'src', 'pdfme_template', fileName);
const raw = fs.readFileSync(absolutePath, 'utf8');
return {
filePath: `src/pdfme_template/${fileName}`,
templateName: `${organizationName} Quotation Standard`,
schemaJson: JSON.parse(raw)
};
}
async function upsertMasterOptionsForOrganization(
sql: SqlClient,
organizationId: string
@@ -382,6 +539,188 @@ async function upsertApprovalWorkflowsForOrganization(sql: SqlClient, organizati
}
}
async function upsertDocumentTemplatesForOrganization(
sql: SqlClient,
organization: { id: string; name: string; createdBy: string }
) {
const templateSeed = loadTemplateSchemaByOrganizationName(organization.name);
const [templateRow] = await sql`
insert into crm_document_templates (
id,
organization_id,
document_type,
product_type,
file_type,
template_name,
description,
is_default,
is_active,
deleted_at,
created_by,
updated_by
) values (
${crypto.randomUUID()},
${organization.id},
${'quotation'},
${'default'},
${'pdfme'},
${templateSeed.templateName},
${'Seeded quotation template metadata for production preview foundation.'},
${true},
${true},
${null},
${organization.createdBy},
${organization.createdBy}
)
on conflict do nothing
returning id
`;
const resolvedTemplateId =
templateRow?.id ??
(
await sql`
select id
from crm_document_templates
where organization_id = ${organization.id}
and document_type = ${'quotation'}
and product_type = ${'default'}
and file_type = ${'pdfme'}
and deleted_at is null
order by is_default desc, created_at asc
limit 1
`
)[0]?.id;
if (!resolvedTemplateId) {
throw new Error(`Unable to resolve template id for organization ${organization.id}`);
}
const [versionRow] = await sql`
insert into crm_document_template_versions (
id,
organization_id,
template_id,
version,
file_path,
schema_json,
preview_image_url,
is_active,
deleted_at,
created_by
) values (
${crypto.randomUUID()},
${organization.id},
${resolvedTemplateId},
${'1.0'},
${templateSeed.filePath},
${JSON.stringify(templateSeed.schemaJson)},
${null},
${true},
${null},
${organization.createdBy}
)
on conflict do nothing
returning id
`;
const resolvedVersionId =
versionRow?.id ??
(
await sql`
select id
from crm_document_template_versions
where organization_id = ${organization.id}
and template_id = ${resolvedTemplateId}
and version = ${'1.0'}
and deleted_at is null
order by created_at asc
limit 1
`
)[0]?.id;
if (!resolvedVersionId) {
throw new Error(`Unable to resolve template version id for organization ${organization.id}`);
}
for (const mapping of DOCUMENT_TEMPLATE_MAPPINGS) {
const [mappingRow] = await sql`
insert into crm_document_template_mappings (
id,
organization_id,
template_version_id,
placeholder_key,
source_path,
data_type,
sheet_name,
default_value,
format_mask,
sort_order,
deleted_at
) values (
${crypto.randomUUID()},
${organization.id},
${resolvedVersionId},
${mapping.placeholderKey},
${mapping.sourcePath},
${mapping.dataType},
${null},
${mapping.defaultValue ?? null},
${mapping.formatMask ?? null},
${mapping.sortOrder},
${null}
)
on conflict do nothing
returning id
`;
const resolvedMappingId =
mappingRow?.id ??
(
await sql`
select id
from crm_document_template_mappings
where organization_id = ${organization.id}
and template_version_id = ${resolvedVersionId}
and placeholder_key = ${mapping.placeholderKey}
and deleted_at is null
limit 1
`
)[0]?.id;
if (!resolvedMappingId || !mapping.columns?.length) {
continue;
}
for (const column of mapping.columns) {
await sql`
insert into crm_document_template_table_columns (
id,
organization_id,
mapping_id,
column_name,
source_field,
column_letter,
sort_order,
format_mask,
deleted_at
) values (
${crypto.randomUUID()},
${organization.id},
${resolvedMappingId},
${column.columnName},
${column.sourceField},
${column.columnLetter ?? null},
${column.sortOrder},
${column.formatMask ?? null},
${null}
)
on conflict do nothing
`;
}
}
}
async function main() {
loadLocalEnv();
@@ -390,7 +729,7 @@ async function main() {
try {
const organizations = await sql`
select id, name
select id, name, created_by as "createdBy"
from organizations
order by name asc
`;
@@ -405,6 +744,7 @@ async function main() {
const branchRows = await upsertMasterOptionsForOrganization(sql, organization.id);
await upsertDocumentSequencesForOrganization(sql, organization.id, branchRows);
await upsertApprovalWorkflowsForOrganization(sql, organization.id);
await upsertDocumentTemplatesForOrganization(sql, organization);
console.log(`[seed-foundation] Seeded foundation data for ${organization.name}`);
}
} finally {