This commit is contained in:
phaichayon
2026-06-15 16:34:41 +07:00
parent 1d2406483e
commit eae0dee1f2
40 changed files with 9982 additions and 132 deletions

View File

@@ -246,3 +246,149 @@ export const crmEnquiryFollowups = pgTable('crm_enquiry_followups', {
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 })
});