Files
alla-allaos-fullstack/src/db/schema.ts
phaichayon eae0dee1f2 task-e
2026-06-15 16:34:41 +07:00

395 lines
16 KiB
TypeScript

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