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('viewer').notNull(), permissions: text('permissions').array().default([]).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); 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 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'), 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 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 crmEnquiries = pgTable( 'crm_enquiries', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), branchId: text('branch_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(), 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(), 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_enquiries_org_code_idx').on( table.organizationId, table.code ) }) ); export const crmEnquiryFollowups = pgTable('crm_enquiry_followups', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), enquiryId: text('enquiry_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 crmQuotations = pgTable( 'crm_quotations', { id: text('id').primaryKey(), organizationId: text('organization_id').notNull(), branchId: text('branch_id'), code: text('code').notNull(), enquiryId: text('enquiry_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'), 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(), 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 }) });