This commit is contained in:
phaichayon
2026-06-16 09:19:17 +07:00
parent b8f13e36b3
commit dff22d75b5
32 changed files with 5786 additions and 68 deletions

View File

@@ -392,3 +392,76 @@ export const crmQuotationAttachments = pgTable('crm_quotation_attachments', {
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
deletedAt: timestamp('deleted_at', { withTimezone: true })
});
export const crmApprovalWorkflows = pgTable(
'crm_approval_workflows',
{
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
code: text('code').notNull(),
name: text('name').notNull(),
entityType: text('entity_type').notNull(),
isActive: boolean('is_active').default(true).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
deletedAt: timestamp('deleted_at', { withTimezone: true })
},
(table) => ({
organizationCodeIdx: uniqueIndex('crm_approval_workflows_org_code_idx').on(
table.organizationId,
table.code
)
})
);
export const crmApprovalSteps = pgTable(
'crm_approval_steps',
{
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
workflowId: text('workflow_id').notNull(),
stepNumber: integer('step_number').notNull(),
roleCode: text('role_code').notNull(),
roleName: text('role_name').notNull(),
isRequired: boolean('is_required').default(true).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
deletedAt: timestamp('deleted_at', { withTimezone: true })
},
(table) => ({
workflowStepIdx: uniqueIndex('crm_approval_steps_workflow_step_idx').on(
table.workflowId,
table.stepNumber
)
})
);
export const crmApprovalRequests = pgTable('crm_approval_requests', {
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
workflowId: text('workflow_id').notNull(),
entityType: text('entity_type').notNull(),
entityId: text('entity_id').notNull(),
currentStep: integer('current_step').default(1).notNull(),
status: text('status').notNull(),
requestedBy: text('requested_by').notNull(),
requestedAt: timestamp('requested_at', { withTimezone: true }).defaultNow().notNull(),
completedAt: timestamp('completed_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
deletedAt: timestamp('deleted_at', { withTimezone: true })
});
export const crmApprovalActions = pgTable('crm_approval_actions', {
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
approvalRequestId: text('approval_request_id').notNull(),
stepNumber: integer('step_number').notNull(),
action: text('action').notNull(),
remark: text('remark'),
actedBy: text('acted_by').notNull(),
actedAt: timestamp('acted_at', { withTimezone: true }).defaultNow().notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
deletedAt: timestamp('deleted_at', { withTimezone: true })
});

View File

@@ -200,6 +200,19 @@ const DOCUMENT_SEQUENCES = [
{ documentType: 'approval', prefix: 'APV' }
];
const APPROVAL_WORKFLOWS = [
{
code: 'quotation_standard_approval',
name: 'Quotation Standard Approval',
entityType: 'quotation',
steps: [
{ stepNumber: 1, roleCode: 'sales_manager', roleName: 'Sales Manager' },
{ stepNumber: 2, roleCode: 'department_manager', roleName: 'Department Manager' },
{ stepNumber: 3, roleCode: 'top_manager', roleName: 'Top Manager' }
]
}
];
async function upsertMasterOptionsForOrganization(
sql: SqlClient,
organizationId: string
@@ -306,6 +319,69 @@ async function upsertDocumentSequencesForOrganization(
}
}
async function upsertApprovalWorkflowsForOrganization(sql: SqlClient, organizationId: string) {
for (const workflow of APPROVAL_WORKFLOWS) {
const [workflowRow] = await sql`
insert into crm_approval_workflows (
id,
organization_id,
code,
name,
entity_type,
is_active,
deleted_at
) values (
${crypto.randomUUID()},
${organizationId},
${workflow.code},
${workflow.name},
${workflow.entityType},
${true},
${null}
)
on conflict (organization_id, code) do update
set
name = excluded.name,
entity_type = excluded.entity_type,
is_active = excluded.is_active,
deleted_at = excluded.deleted_at,
updated_at = now()
returning id
`;
for (const step of workflow.steps) {
await sql`
insert into crm_approval_steps (
id,
organization_id,
workflow_id,
step_number,
role_code,
role_name,
is_required,
deleted_at
) values (
${crypto.randomUUID()},
${organizationId},
${workflowRow.id},
${step.stepNumber},
${step.roleCode},
${step.roleName},
${true},
${null}
)
on conflict (workflow_id, step_number) do update
set
role_code = excluded.role_code,
role_name = excluded.role_name,
is_required = excluded.is_required,
deleted_at = excluded.deleted_at,
updated_at = now()
`;
}
}
}
async function main() {
loadLocalEnv();
@@ -328,6 +404,7 @@ async function main() {
for (const organization of organizations) {
const branchRows = await upsertMasterOptionsForOrganization(sql, organization.id);
await upsertDocumentSequencesForOrganization(sql, organization.id, branchRows);
await upsertApprovalWorkflowsForOrganization(sql, organization.id);
console.log(`[seed-foundation] Seeded foundation data for ${organization.name}`);
}
} finally {