import { boolean, doublePrecision, integer, jsonb, pgTable, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'; export const users = pgTable( 'users', { id: text('id').primaryKey(), name: text('name').notNull(), email: text('email').notNull(), passwordHash: text('password_hash').notNull(), systemRole: text('system_role').default('user').notNull(), image: text('image'), activeOrganizationId: text('active_organization_id'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ emailIdx: uniqueIndex('users_email_idx').on(table.email) }) ); export const organizations = pgTable( 'organizations', { id: text('id').primaryKey(), name: text('name').notNull(), slug: text('slug').notNull(), imageUrl: text('image_url'), plan: text('plan').default('free').notNull(), createdBy: text('created_by').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ slugIdx: uniqueIndex('organizations_slug_idx').on(table.slug) }) ); export const memberships = pgTable('memberships', { id: text('id').primaryKey(), userId: text('user_id').notNull(), organizationId: text('organization_id').notNull(), role: text('role').default('user').notNull(), businessRole: text('business_role').default('sales_support').notNull(), permissions: text('permissions').array().default([]).notNull(), branchScopeIds: text('branch_scope_ids').array().default([]).notNull(), productTypeScopeIds: text('product_type_scope_ids').array().default([]).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); export const crmRoleProfiles = pgTable( 'crm_role_profiles', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), code: text('code').notNull(), name: text('name').notNull(), description: text('description'), permissions: text('permissions').array().default([]).notNull(), branchScopeMode: text('branch_scope_mode').default('assigned').notNull(), productScopeMode: text('product_scope_mode').default('assigned').notNull(), ownershipScope: text('ownership_scope').default('own').notNull(), approvalAuthority: text('approval_authority').default('none').notNull(), isSystem: boolean('is_system').default(false).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 }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }, (table) => ({ organizationRoleCodeIdx: uniqueIndex('crm_role_profiles_org_code_idx').on( table.organizationId, table.code ) }) ); export const crmUserRoleAssignments = pgTable( 'crm_user_role_assignments', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), userId: text('user_id').notNull(), roleProfileId: text('role_profile_id').notNull(), branchScopeMode: text('branch_scope_mode').default('inherit').notNull(), branchScopeIds: text('branch_scope_ids').array().default([]).notNull(), productTypeScopeMode: text('product_type_scope_mode').default('inherit').notNull(), productTypeScopeIds: text('product_type_scope_ids').array().default([]).notNull(), isPrimary: boolean('is_primary').default(false).notNull(), isActive: boolean('is_active').default(true).notNull(), assignedBy: text('assigned_by').notNull(), assignedAt: timestamp('assigned_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 }) }, (table) => ({ organizationUserRoleProfileIdx: uniqueIndex('crm_user_role_assignments_org_user_role_idx').on( table.organizationId, table.userId, table.roleProfileId ) }) ); export const products = pgTable('products', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id').notNull(), name: text('name').notNull(), category: text('category').notNull(), description: text('description').notNull(), photoUrl: text('photo_url').notNull(), price: doublePrecision('price').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); export const msOptions = pgTable( 'ms_options', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), category: text('category').notNull(), code: text('code').notNull(), label: text('label').notNull(), value: text('value'), parentId: text('parent_id'), sortOrder: integer('sort_order').default(0).notNull(), isActive: boolean('is_active').default(true).notNull(), metadata: jsonb('metadata'), deletedAt: timestamp('deleted_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ organizationCategoryCodeIdx: uniqueIndex('ms_options_org_category_code_idx').on( table.organizationId, table.category, table.code ) }) ); export const documentSequences = pgTable( 'document_sequences', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), branchId: text('branch_id').default('').notNull(), documentType: text('document_type').notNull(), prefix: text('prefix').notNull(), period: text('period').notNull(), currentNumber: integer('current_number').default(0).notNull(), paddingLength: integer('padding_length').default(3).notNull(), isActive: boolean('is_active').default(true).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ organizationDocumentPeriodBranchIdx: uniqueIndex( 'document_sequences_org_doc_period_branch_idx' ).on(table.organizationId, table.documentType, table.period, table.branchId) }) ); export const trAuditLogs = pgTable('tr_audit_logs', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), branchId: text('branch_id'), userId: text('user_id').notNull(), entityType: text('entity_type').notNull(), entityId: text('entity_id').notNull(), action: text('action').notNull(), beforeData: jsonb('before_data'), afterData: jsonb('after_data'), requestId: text('request_id'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() }); export const appNotificationEvents = pgTable( 'app_notification_events', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), eventType: text('event_type').notNull(), entityType: text('entity_type').notNull(), entityId: text('entity_id').notNull(), actorUserId: text('actor_user_id'), payload: jsonb('payload'), dedupeKey: text('dedupe_key'), status: text('status').default('pending').notNull(), lastError: text('last_error'), publishedAt: timestamp('published_at', { withTimezone: true }).defaultNow().notNull(), processedAt: timestamp('processed_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ organizationDedupeIdx: uniqueIndex('app_notification_events_org_dedupe_idx').on( table.organizationId, table.dedupeKey ) }) ); export const appNotifications = pgTable( 'app_notifications', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), recipientUserId: text('recipient_user_id').notNull(), eventId: text('event_id').notNull(), title: text('title').notNull(), body: text('body').notNull(), linkUrl: text('link_url'), severity: text('severity').default('info').notNull(), status: text('status').default('unread').notNull(), readAt: timestamp('read_at', { withTimezone: true }), archivedAt: timestamp('archived_at', { withTimezone: true }), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ recipientEventIdx: uniqueIndex('app_notifications_recipient_event_idx').on( table.recipientUserId, table.eventId ) }) ); export const appNotificationTemplates = pgTable( 'app_notification_templates', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), eventType: text('event_type').notNull(), channel: text('channel').notNull(), titleTemplate: text('title_template').notNull(), bodyTemplate: text('body_template').notNull(), linkTemplate: text('link_template'), isActive: boolean('is_active').default(true).notNull(), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }, (table) => ({ organizationEventChannelIdx: uniqueIndex('app_notification_templates_org_event_channel_idx').on( table.organizationId, table.eventType, table.channel ) }) ); export const appNotificationDeliveries = pgTable('app_notification_deliveries', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), notificationId: text('notification_id').notNull(), channel: text('channel').notNull(), recipientAddress: text('recipient_address'), status: text('status').default('pending').notNull(), attemptCount: integer('attempt_count').default(0).notNull(), lastError: text('last_error'), sentAt: timestamp('sent_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); export const crmCustomers = pgTable( 'crm_customers', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), branchId: text('branch_id'), code: text('code').notNull(), name: text('name').notNull(), abbr: text('abbr'), taxId: text('tax_id'), customerType: text('customer_type').notNull(), customerStatus: text('customer_status').notNull(), address: text('address'), province: text('province'), district: text('district'), subDistrict: text('sub_district'), postalCode: text('postal_code'), country: text('country'), phone: text('phone'), fax: text('fax'), email: text('email'), website: text('website'), leadChannel: text('lead_channel'), customerGroup: text('customer_group'), customerSubGroup: text('customer_sub_group'), ownerUserId: text('owner_user_id'), ownerAssignedAt: timestamp('owner_assigned_at', { withTimezone: true }), ownerAssignedBy: text('owner_assigned_by'), notes: text('notes'), 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 }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }, (table) => ({ organizationCodeIdx: uniqueIndex('crm_customers_org_code_idx').on( table.organizationId, table.code ) }) ); export const crmCustomerOwnerHistory = pgTable('crm_customer_owner_history', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), customerId: text('customer_id').notNull(), oldOwnerUserId: text('old_owner_user_id'), newOwnerUserId: text('new_owner_user_id'), changedBy: text('changed_by').notNull(), changedAt: timestamp('changed_at', { withTimezone: true }).defaultNow().notNull(), remark: text('remark'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }); export const crmCustomerContacts = pgTable('crm_customer_contacts', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), customerId: text('customer_id').notNull(), name: text('name').notNull(), position: text('position'), department: text('department'), phone: text('phone'), mobile: text('mobile'), email: text('email'), isPrimary: boolean('is_primary').default(false).notNull(), notes: text('notes'), 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 }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }); export const crmLeads = pgTable( 'crm_leads', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), branchId: text('branch_id'), code: text('code').notNull(), customerId: text('customer_id'), contactId: text('contact_id'), description: text('description'), projectName: text('project_name'), projectLocation: text('project_location'), productType: text('product_type'), priority: text('priority'), estimatedValue: doublePrecision('estimated_value'), awarenessId: text('awareness_id'), status: text('status').default('new_job').notNull(), followupStatus: text('followup_status'), lostReason: text('lost_reason'), outcome: text('outcome').default('open').notNull(), ownerMarketingUserId: text('owner_marketing_user_id'), assignedSalesOwnerId: text('assigned_sales_owner_id'), assignedAt: timestamp('assigned_at', { withTimezone: true }), assignedBy: text('assigned_by'), assignmentRemark: text('assignment_remark'), createdBy: text('created_by').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_leads_org_code_idx').on(table.organizationId, table.code) }) ); export const crmContactShares = pgTable( 'crm_contact_shares', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), contactId: text('contact_id').notNull(), sharedToUserId: text('shared_to_user_id').notNull(), sharedByUserId: text('shared_by_user_id').notNull(), sharedAt: timestamp('shared_at', { withTimezone: true }).defaultNow().notNull(), isActive: boolean('is_active').default(true).notNull(), remark: text('remark'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }, (table) => ({ contactSharedUserIdx: uniqueIndex('crm_contact_shares_org_contact_shared_user_idx').on( table.organizationId, table.contactId, table.sharedToUserId ) }) ); export const crmOpportunities = pgTable( 'crm_opportunities', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), branchId: text('branch_id'), leadId: text('lead_id'), code: text('code').notNull(), customerId: text('customer_id').notNull(), contactId: text('contact_id'), title: text('title').notNull(), description: text('description'), requirement: text('requirement'), projectName: text('project_name'), projectLocation: text('project_location'), productType: text('product_type').notNull(), status: text('status').notNull(), outcomeStatus: text('outcome_status').default('open').notNull(), priority: text('priority').notNull(), leadChannel: text('lead_channel'), estimatedValue: doublePrecision('estimated_value'), chancePercent: integer('chance_percent'), expectedCloseDate: timestamp('expected_close_date', { withTimezone: true }), competitor: text('competitor'), source: text('source'), notes: text('notes'), isHotProject: boolean('is_hot_project').default(false).notNull(), isActive: boolean('is_active').default(true).notNull(), pipelineStage: text('pipeline_stage').default('lead').notNull(), closedAt: timestamp('closed_at', { withTimezone: true }), closedWonAt: timestamp('closed_won_at', { withTimezone: true }), closedLostAt: timestamp('closed_lost_at', { withTimezone: true }), closedByUserId: text('closed_by_user_id'), poNumber: text('po_number'), poDate: timestamp('po_date', { withTimezone: true }), poAmount: doublePrecision('po_amount'), poCurrency: text('po_currency'), lostReason: text('lost_reason'), lostDetail: text('lost_detail'), lostCompetitor: text('lost_competitor'), lostRemark: text('lost_remark'), cancelReason: text('cancel_reason'), noQuotationReason: text('no_quotation_reason'), assignedToUserId: text('assigned_to_user_id'), assignedAt: timestamp('assigned_at', { withTimezone: true }), assignedBy: text('assigned_by'), assignmentRemark: text('assignment_remark'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }, (table) => ({ organizationCodeIdx: uniqueIndex('crm_opportunities_org_code_idx').on( table.organizationId, table.code ) }) ); export const crmOpportunityFollowups = pgTable('crm_opportunity_followups', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), opportunityId: text('opportunity_id').notNull(), followupDate: timestamp('followup_date', { withTimezone: true }).notNull(), followupType: text('followup_type').notNull(), contactId: text('contact_id'), outcome: text('outcome'), notes: text('notes'), nextFollowupDate: timestamp('next_followup_date', { withTimezone: true }), nextAction: text('next_action'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }); export const crmOpportunityCustomers = pgTable('crm_opportunity_customers', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), opportunityId: text('opportunity_id').notNull(), customerId: text('customer_id').notNull(), role: text('role').notNull(), remark: text('remark'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }); export const crmOpportunityAttachments = pgTable('crm_opportunity_attachments', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), opportunityId: text('opportunity_id').notNull(), category: text('category').notNull(), fileName: text('file_name').notNull(), originalFileName: text('original_file_name').notNull(), storageProvider: text('storage_provider').notNull(), storageKey: text('storage_key').notNull(), fileSize: integer('file_size'), fileType: text('file_type'), description: text('description'), uploadedAt: timestamp('uploaded_at', { withTimezone: true }).defaultNow().notNull(), uploadedBy: text('uploaded_by').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }); export const crmReportDefinitions = pgTable( 'crm_report_definitions', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), code: text('code').notNull(), name: text('name').notNull(), description: text('description'), category: text('category').notNull(), isSystem: boolean('is_system').default(true).notNull(), isActive: boolean('is_active').default(true).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ organizationCodeIdx: uniqueIndex('crm_report_definitions_org_code_idx').on( table.organizationId, table.code ) }) ); export const crmQuotations = pgTable( 'crm_quotations', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), branchId: text('branch_id'), code: text('code').notNull(), opportunityId: text('opportunity_id'), customerId: text('customer_id').notNull(), contactId: text('contact_id'), quotationDate: timestamp('quotation_date', { withTimezone: true }).notNull(), validUntil: timestamp('valid_until', { withTimezone: true }), quotationType: text('quotation_type').notNull(), projectName: text('project_name'), projectLocation: text('project_location'), attention: text('attention'), reference: text('reference'), notes: text('notes'), status: text('status').notNull(), revision: integer('revision').default(0).notNull(), parentQuotationId: text('parent_quotation_id'), revisionRemark: text('revision_remark'), currency: text('currency').notNull(), exchangeRate: doublePrecision('exchange_rate').default(1).notNull(), subtotal: doublePrecision('subtotal').default(0).notNull(), discount: doublePrecision('discount').default(0).notNull(), discountType: text('discount_type'), taxRate: doublePrecision('tax_rate').default(0).notNull(), taxAmount: doublePrecision('tax_amount').default(0).notNull(), totalAmount: doublePrecision('total_amount').default(0).notNull(), chancePercent: integer('chance_percent'), isHotProject: boolean('is_hot_project').default(false).notNull(), competitor: text('competitor'), salesmanId: text('salesman_id'), isSent: boolean('is_sent').default(false).notNull(), sentAt: timestamp('sent_at', { withTimezone: true }), sentVia: text('sent_via'), approvedAt: timestamp('approved_at', { withTimezone: true }), approvedArtifactId: text('approved_artifact_id'), approvedPdfUrl: text('approved_pdf_url'), approvedSnapshot: jsonb('approved_snapshot'), approvedTemplateVersionId: text('approved_template_version_id'), acceptedAt: timestamp('accepted_at', { withTimezone: true }), rejectedAt: timestamp('rejected_at', { withTimezone: true }), rejectionReason: text('rejection_reason'), 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 }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }, (table) => ({ organizationCodeIdx: uniqueIndex('crm_quotations_org_code_idx').on( table.organizationId, table.code ) }) ); export const crmQuotationItems = pgTable('crm_quotation_items', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), quotationId: text('quotation_id').notNull(), itemNumber: integer('item_number').notNull(), productType: text('product_type').notNull(), description: text('description').notNull(), quantity: doublePrecision('quantity').default(0).notNull(), unit: text('unit'), unitPrice: doublePrecision('unit_price').default(0).notNull(), discount: doublePrecision('discount').default(0).notNull(), discountType: text('discount_type'), taxRate: doublePrecision('tax_rate').default(0).notNull(), totalPrice: doublePrecision('total_price').default(0).notNull(), notes: text('notes'), sortOrder: integer('sort_order').default(0).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }); export const crmQuotationCustomers = pgTable('crm_quotation_customers', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), quotationId: text('quotation_id').notNull(), customerId: text('customer_id').notNull(), role: text('role').notNull(), isPrimary: boolean('is_primary').default(false).notNull(), remark: text('remark'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }); export const crmQuotationTopics = pgTable('crm_quotation_topics', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), quotationId: text('quotation_id').notNull(), topicType: text('topic_type').notNull(), title: text('title').notNull(), sortOrder: integer('sort_order').default(0).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }); export const crmQuotationTopicItems = pgTable('crm_quotation_topic_items', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), topicId: text('topic_id').notNull(), content: text('content').notNull(), sortOrder: integer('sort_order').default(0).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }); export const crmQuotationFollowups = pgTable('crm_quotation_followups', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), quotationId: text('quotation_id').notNull(), followupDate: timestamp('followup_date', { withTimezone: true }).notNull(), followupType: text('followup_type').notNull(), contactId: text('contact_id'), outcome: text('outcome'), notes: text('notes'), nextFollowupDate: timestamp('next_followup_date', { withTimezone: true }), nextAction: text('next_action'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }); export const crmQuotationAttachments = pgTable('crm_quotation_attachments', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), quotationId: text('quotation_id').notNull(), fileName: text('file_name').notNull(), originalFileName: text('original_file_name').notNull(), filePath: text('file_path').notNull(), fileSize: integer('file_size'), fileType: text('file_type'), description: text('description'), uploadedAt: timestamp('uploaded_at', { withTimezone: true }).defaultNow().notNull(), uploadedBy: text('uploaded_by').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }); export const crmDocumentArtifacts = pgTable('crm_document_artifacts', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), entityType: text('entity_type').notNull(), entityId: text('entity_id').notNull(), documentType: text('document_type').notNull(), artifactType: text('artifact_type').notNull(), templateVersionId: text('template_version_id'), storageProvider: text('storage_provider').notNull(), storageKey: text('storage_key').notNull(), fileName: text('file_name').notNull(), contentType: text('content_type').notNull(), fileSize: integer('file_size'), checksum: text('checksum'), status: text('status').default('active').notNull(), generatedBy: text('generated_by').notNull(), generatedAt: timestamp('generated_at', { withTimezone: true }).defaultNow().notNull(), lockedAt: timestamp('locked_at', { withTimezone: true }), lockedBy: text('locked_by'), voidedAt: timestamp('voided_at', { withTimezone: true }), voidedBy: text('voided_by'), voidReason: text('void_reason'), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), 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(), description: text('description'), isSystem: boolean('is_system').default(false).notNull(), isActive: boolean('is_active').default(true).notNull(), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').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(), approvalMode: text('approval_mode').default('sequential').notNull(), isRequired: boolean('is_required').default(true).notNull(), slaHours: integer('sla_hours').default(24).notNull(), calendarMode: text('calendar_mode').default('calendar_days').notNull(), timeoutAction: text('timeout_action').default('none').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 crmApprovalMatrices = pgTable('crm_approval_matrices', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), entityType: text('entity_type').notNull(), workflowId: text('workflow_id').notNull(), branchId: text('branch_id'), productType: text('product_type'), minAmount: doublePrecision('min_amount'), maxAmount: doublePrecision('max_amount'), currency: text('currency'), priority: integer('priority').default(100).notNull(), isDefault: boolean('is_default').default(false).notNull(), isActive: boolean('is_active').default(true).notNull(), description: text('description'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }); 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(), currentStepStartedAt: timestamp('current_step_started_at', { withTimezone: true }) .defaultNow() .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 crmApprovalReminderPolicies = pgTable( 'crm_approval_reminder_policies', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), workflowId: text('workflow_id').notNull(), stepNumber: integer('step_number').notNull(), slaHours: integer('sla_hours').default(24).notNull(), reminderOffsetsHours: integer('reminder_offsets_hours').array().default([]).notNull(), calendarMode: text('calendar_mode').default('calendar_days').notNull(), timeoutAction: text('timeout_action').default('none').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 }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }, (table) => ({ workflowStepIdx: uniqueIndex('crm_approval_reminder_policies_workflow_step_idx').on( table.workflowId, table.stepNumber ) }) ); export const crmApprovalEscalationPolicies = pgTable('crm_approval_escalation_policies', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), workflowId: text('workflow_id').notNull(), stepNumber: integer('step_number').notNull(), triggerAfterHours: integer('trigger_after_hours').notNull(), targetType: text('target_type').notNull(), targetValue: text('target_value'), 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 }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }); 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 }) }); export const crmDocumentTemplates = pgTable( 'crm_document_templates', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), documentType: text('document_type').notNull(), productType: text('product_type').notNull(), fileType: text('file_type').notNull(), templateName: text('template_name').notNull(), description: text('description'), isDefault: boolean('is_default').default(false).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 }), createdBy: text('created_by').notNull(), updatedBy: text('updated_by').notNull() }, (table) => ({ organizationTemplateIdx: uniqueIndex('crm_document_templates_org_doc_product_file_name_idx').on( table.organizationId, table.documentType, table.productType, table.fileType, table.templateName ) }) ); export const crmDocumentTemplateVersions = pgTable( 'crm_document_template_versions', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), templateId: text('template_id').notNull(), version: text('version').notNull(), filePath: text('file_path'), schemaJson: jsonb('schema_json').notNull(), previewImageUrl: text('preview_image_url'), 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 }), createdBy: text('created_by').notNull() }, (table) => ({ templateVersionIdx: uniqueIndex('crm_document_template_versions_template_version_idx').on( table.templateId, table.version ) }) ); export const crmDocumentTemplateMappings = pgTable( 'crm_document_template_mappings', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), templateVersionId: text('template_version_id').notNull(), placeholderKey: text('placeholder_key').notNull(), sourcePath: text('source_path').notNull(), dataType: text('data_type').notNull(), sheetName: text('sheet_name'), defaultValue: text('default_value'), formatMask: text('format_mask'), sortOrder: integer('sort_order').default(0).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }, (table) => ({ templatePlaceholderIdx: uniqueIndex( 'crm_document_template_mappings_version_placeholder_idx' ).on(table.templateVersionId, table.placeholderKey) }) ); export const crmDocumentTemplateTableColumns = pgTable( 'crm_document_template_table_columns', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), mappingId: text('mapping_id').notNull(), columnName: text('column_name').notNull(), sourceField: text('source_field').notNull(), columnLetter: text('column_letter'), sortOrder: integer('sort_order').default(0).notNull(), formatMask: text('format_mask'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }) }, (table) => ({ mappingColumnIdx: uniqueIndex('crm_document_template_table_columns_mapping_name_idx').on( table.mappingId, table.columnName ) }) );