@nextsparkjs/theme-crm 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CRM_PLAN.md +343 -0
- package/about.md +122 -0
- package/config/app.config.ts +185 -0
- package/config/billing.config.ts +187 -0
- package/config/dashboard.config.ts +372 -0
- package/config/dev.config.ts +55 -0
- package/config/features.config.ts +336 -0
- package/config/flows.config.ts +511 -0
- package/config/permissions.config.ts +297 -0
- package/config/theme.config.ts +111 -0
- package/entities/activities/activities.config.ts +61 -0
- package/entities/activities/activities.fields.ts +362 -0
- package/entities/activities/activities.service.ts +503 -0
- package/entities/activities/activities.types.ts +117 -0
- package/entities/activities/messages/en.json +123 -0
- package/entities/activities/messages/es.json +123 -0
- package/entities/activities/migrations/020_activities_table.sql +123 -0
- package/entities/activities/migrations/021_activities_metas.sql +114 -0
- package/entities/activities/migrations/022_activities_sample_data.sql +420 -0
- package/entities/campaigns/campaigns.config.ts +61 -0
- package/entities/campaigns/campaigns.fields.ts +413 -0
- package/entities/campaigns/campaigns.service.ts +426 -0
- package/entities/campaigns/campaigns.types.ts +124 -0
- package/entities/campaigns/messages/en.json +145 -0
- package/entities/campaigns/messages/es.json +145 -0
- package/entities/campaigns/migrations/001_campaigns_table.sql +127 -0
- package/entities/campaigns/migrations/002_campaigns_metas.sql +114 -0
- package/entities/campaigns/migrations/003_campaigns_sample_data.sql +364 -0
- package/entities/companies/companies.config.ts +61 -0
- package/entities/companies/companies.fields.ts +429 -0
- package/entities/companies/companies.service.ts +566 -0
- package/entities/companies/companies.types.ts +125 -0
- package/entities/companies/messages/en.json +146 -0
- package/entities/companies/messages/es.json +146 -0
- package/entities/companies/migrations/001_companies_table.sql +150 -0
- package/entities/companies/migrations/002_companies_metas.sql +114 -0
- package/entities/companies/migrations/003_companies_sample_data.sql +246 -0
- package/entities/contacts/contacts.config.ts +61 -0
- package/entities/contacts/contacts.fields.ts +359 -0
- package/entities/contacts/contacts.service.ts +509 -0
- package/entities/contacts/contacts.types.ts +108 -0
- package/entities/contacts/messages/en.json +117 -0
- package/entities/contacts/messages/es.json +117 -0
- package/entities/contacts/migrations/001_contacts_table.sql +134 -0
- package/entities/contacts/migrations/002_contacts_metas.sql +114 -0
- package/entities/contacts/migrations/003_contacts_sample_data.sql +421 -0
- package/entities/leads/leads.config.ts +61 -0
- package/entities/leads/leads.fields.ts +336 -0
- package/entities/leads/leads.service.ts +496 -0
- package/entities/leads/leads.types.ts +114 -0
- package/entities/leads/messages/en.json +132 -0
- package/entities/leads/messages/es.json +132 -0
- package/entities/leads/migrations/001_leads_table.sql +150 -0
- package/entities/leads/migrations/002_leads_metas.sql +120 -0
- package/entities/leads/migrations/003_leads_sample_data.sql +242 -0
- package/entities/notes/messages/en.json +114 -0
- package/entities/notes/messages/es.json +114 -0
- package/entities/notes/migrations/020_notes_table.sql +118 -0
- package/entities/notes/migrations/021_notes_metas.sql +114 -0
- package/entities/notes/migrations/022_notes_sample_data.sql +275 -0
- package/entities/notes/notes.config.ts +61 -0
- package/entities/notes/notes.fields.ts +283 -0
- package/entities/notes/notes.service.ts +320 -0
- package/entities/notes/notes.types.ts +102 -0
- package/entities/opportunities/messages/en.json +107 -0
- package/entities/opportunities/messages/es.json +107 -0
- package/entities/opportunities/migrations/010_opportunities_table.sql +145 -0
- package/entities/opportunities/migrations/011_opportunities_metas.sql +114 -0
- package/entities/opportunities/migrations/012_opportunities_sample_data.sql +438 -0
- package/entities/opportunities/opportunities.config.ts +61 -0
- package/entities/opportunities/opportunities.fields.ts +416 -0
- package/entities/opportunities/opportunities.service.ts +525 -0
- package/entities/opportunities/opportunities.types.ts +135 -0
- package/entities/pipelines/messages/en.json +115 -0
- package/entities/pipelines/messages/es.json +115 -0
- package/entities/pipelines/migrations/001_pipelines_table.sql +106 -0
- package/entities/pipelines/migrations/002_pipelines_metas.sql +114 -0
- package/entities/pipelines/migrations/003_pipelines_sample_data.sql +91 -0
- package/entities/pipelines/pipelines.config.ts +62 -0
- package/entities/pipelines/pipelines.fields.ts +193 -0
- package/entities/pipelines/pipelines.service.ts +383 -0
- package/entities/pipelines/pipelines.types.ts +78 -0
- package/entities/products/messages/en.json +135 -0
- package/entities/products/messages/es.json +135 -0
- package/entities/products/migrations/001_products_table.sql +117 -0
- package/entities/products/migrations/002_products_metas.sql +114 -0
- package/entities/products/migrations/003_products_sample_data.sql +247 -0
- package/entities/products/products.config.ts +62 -0
- package/entities/products/products.fields.ts +361 -0
- package/entities/products/products.service.ts +437 -0
- package/entities/products/products.types.ts +125 -0
- package/lib/crm-constants.ts +77 -0
- package/lib/crm-utils.ts +185 -0
- package/lib/selectors.ts +333 -0
- package/messages/en.json +131 -0
- package/messages/es.json +131 -0
- package/migrations/999_theme_sample_data.sql +473 -0
- package/package.json +18 -0
- package/pendings.md +205 -0
- package/permissions-matrix.md +216 -0
- package/styles/components.css +414 -0
- package/styles/crm-theme.css +358 -0
- package/styles/globals.css +576 -0
- package/styles/variables.css +111 -0
- package/templates/dashboard/(main)/activities/components/ActivityCard.tsx +169 -0
- package/templates/dashboard/(main)/activities/components/ActivityTimeline.tsx +165 -0
- package/templates/dashboard/(main)/activities/page.tsx +297 -0
- package/templates/dashboard/(main)/campaigns/page.tsx +373 -0
- package/templates/dashboard/(main)/companies/page.tsx +296 -0
- package/templates/dashboard/(main)/contacts/page.tsx +347 -0
- package/templates/dashboard/(main)/layout.tsx +98 -0
- package/templates/dashboard/(main)/leads/page.tsx +335 -0
- package/templates/dashboard/(main)/opportunities/[id]/edit/page.tsx +95 -0
- package/templates/dashboard/(main)/opportunities/create/page.tsx +94 -0
- package/templates/dashboard/(main)/opportunities/page.tsx +350 -0
- package/templates/dashboard/(main)/pipelines/[id]/edit/page.tsx +95 -0
- package/templates/dashboard/(main)/pipelines/[id]/page.tsx +143 -0
- package/templates/dashboard/(main)/pipelines/create/page.tsx +94 -0
- package/templates/dashboard/(main)/pipelines/page.tsx +234 -0
- package/templates/dashboard/(main)/products/[id]/edit/page.tsx +97 -0
- package/templates/dashboard/(main)/products/[id]/page.tsx +509 -0
- package/templates/dashboard/(main)/products/create/page.tsx +96 -0
- package/templates/dashboard/(main)/products/page.tsx +308 -0
- package/templates/shared/ActionButtons.tsx +41 -0
- package/templates/shared/CRMDashboard.tsx +519 -0
- package/templates/shared/CRMDataTable.tsx +441 -0
- package/templates/shared/CRMMetricCard.tsx +76 -0
- package/templates/shared/CRMMobileNav.tsx +172 -0
- package/templates/shared/CRMSidebar.tsx +346 -0
- package/templates/shared/CRMTopBar.tsx +265 -0
- package/templates/shared/DealCard.tsx +123 -0
- package/templates/shared/EntityCard.tsx +58 -0
- package/templates/shared/OpportunityForm.tsx +649 -0
- package/templates/shared/PipelineForm.tsx +367 -0
- package/templates/shared/PipelineKanban.tsx +194 -0
- package/templates/shared/QuickFilters.tsx +47 -0
- package/templates/shared/StageColumn.tsx +175 -0
- package/templates/shared/StageSelect.tsx +177 -0
- package/templates/shared/StagesRepeater.tsx +317 -0
- package/templates/shared/index.ts +9 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entity": {
|
|
3
|
+
"name": "Company",
|
|
4
|
+
"plural": "Companies",
|
|
5
|
+
"description": "Manage client companies and prospects"
|
|
6
|
+
},
|
|
7
|
+
"fields": {
|
|
8
|
+
"name": {
|
|
9
|
+
"label": "Company Name",
|
|
10
|
+
"description": "Company display name",
|
|
11
|
+
"placeholder": "Enter company name..."
|
|
12
|
+
},
|
|
13
|
+
"legalName": {
|
|
14
|
+
"label": "Legal Name",
|
|
15
|
+
"description": "Legal registered company name",
|
|
16
|
+
"placeholder": "Enter legal name..."
|
|
17
|
+
},
|
|
18
|
+
"taxId": {
|
|
19
|
+
"label": "Tax ID",
|
|
20
|
+
"description": "Tax identification number",
|
|
21
|
+
"placeholder": "Enter tax ID..."
|
|
22
|
+
},
|
|
23
|
+
"website": {
|
|
24
|
+
"label": "Website",
|
|
25
|
+
"description": "Company website URL",
|
|
26
|
+
"placeholder": "https://example.com"
|
|
27
|
+
},
|
|
28
|
+
"email": {
|
|
29
|
+
"label": "Email",
|
|
30
|
+
"description": "General company email",
|
|
31
|
+
"placeholder": "info@company.com"
|
|
32
|
+
},
|
|
33
|
+
"phone": {
|
|
34
|
+
"label": "Phone",
|
|
35
|
+
"description": "Main company phone number",
|
|
36
|
+
"placeholder": "Enter phone number..."
|
|
37
|
+
},
|
|
38
|
+
"industry": {
|
|
39
|
+
"label": "Industry",
|
|
40
|
+
"description": "Industry sector",
|
|
41
|
+
"placeholder": "Enter industry..."
|
|
42
|
+
},
|
|
43
|
+
"type": {
|
|
44
|
+
"label": "Company Type",
|
|
45
|
+
"description": "Type of company relationship",
|
|
46
|
+
"placeholder": "Select type..."
|
|
47
|
+
},
|
|
48
|
+
"size": {
|
|
49
|
+
"label": "Company Size",
|
|
50
|
+
"description": "Company size by employees",
|
|
51
|
+
"placeholder": "Select size..."
|
|
52
|
+
},
|
|
53
|
+
"annualRevenue": {
|
|
54
|
+
"label": "Annual Revenue",
|
|
55
|
+
"description": "Annual revenue in currency",
|
|
56
|
+
"placeholder": "0.00"
|
|
57
|
+
},
|
|
58
|
+
"address": {
|
|
59
|
+
"label": "Address",
|
|
60
|
+
"description": "Street address",
|
|
61
|
+
"placeholder": "Enter address..."
|
|
62
|
+
},
|
|
63
|
+
"city": {
|
|
64
|
+
"label": "City",
|
|
65
|
+
"description": "City",
|
|
66
|
+
"placeholder": "Enter city..."
|
|
67
|
+
},
|
|
68
|
+
"state": {
|
|
69
|
+
"label": "State",
|
|
70
|
+
"description": "State or province",
|
|
71
|
+
"placeholder": "Enter state..."
|
|
72
|
+
},
|
|
73
|
+
"country": {
|
|
74
|
+
"label": "Country",
|
|
75
|
+
"description": "Country",
|
|
76
|
+
"placeholder": "US"
|
|
77
|
+
},
|
|
78
|
+
"postalCode": {
|
|
79
|
+
"label": "Postal Code",
|
|
80
|
+
"description": "Postal or ZIP code",
|
|
81
|
+
"placeholder": "Enter postal code..."
|
|
82
|
+
},
|
|
83
|
+
"logo": {
|
|
84
|
+
"label": "Logo",
|
|
85
|
+
"description": "Company logo URL",
|
|
86
|
+
"placeholder": "https://example.com/logo.png"
|
|
87
|
+
},
|
|
88
|
+
"rating": {
|
|
89
|
+
"label": "Rating",
|
|
90
|
+
"description": "Sales rating",
|
|
91
|
+
"placeholder": "Select rating..."
|
|
92
|
+
},
|
|
93
|
+
"assignedTo": {
|
|
94
|
+
"label": "Assigned To",
|
|
95
|
+
"description": "Account manager assigned",
|
|
96
|
+
"placeholder": "Select user..."
|
|
97
|
+
},
|
|
98
|
+
"createdAt": {
|
|
99
|
+
"label": "Created At",
|
|
100
|
+
"description": "When the company was created"
|
|
101
|
+
},
|
|
102
|
+
"updatedAt": {
|
|
103
|
+
"label": "Updated At",
|
|
104
|
+
"description": "When the company was last updated"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"options": {
|
|
108
|
+
"type": {
|
|
109
|
+
"prospect": "Prospect",
|
|
110
|
+
"customer": "Customer",
|
|
111
|
+
"partner": "Partner",
|
|
112
|
+
"competitor": "Competitor",
|
|
113
|
+
"vendor": "Vendor",
|
|
114
|
+
"other": "Other"
|
|
115
|
+
},
|
|
116
|
+
"size": {
|
|
117
|
+
"1-10": "1-10 employees",
|
|
118
|
+
"11-50": "11-50 employees",
|
|
119
|
+
"51-200": "51-200 employees",
|
|
120
|
+
"201-500": "201-500 employees",
|
|
121
|
+
"500+": "500+ employees"
|
|
122
|
+
},
|
|
123
|
+
"rating": {
|
|
124
|
+
"hot": "Hot",
|
|
125
|
+
"warm": "Warm",
|
|
126
|
+
"cold": "Cold"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"actions": {
|
|
130
|
+
"create": "Create Company",
|
|
131
|
+
"edit": "Edit Company",
|
|
132
|
+
"delete": "Delete Company",
|
|
133
|
+
"view": "View Company",
|
|
134
|
+
"list": "List Companies",
|
|
135
|
+
"search": "Search Companies",
|
|
136
|
+
"export": "Export Companies",
|
|
137
|
+
"import": "Import Companies"
|
|
138
|
+
},
|
|
139
|
+
"messages": {
|
|
140
|
+
"created": "Company created successfully",
|
|
141
|
+
"updated": "Company updated successfully",
|
|
142
|
+
"deleted": "Company deleted successfully",
|
|
143
|
+
"notFound": "Company not found",
|
|
144
|
+
"error": "An error occurred while processing the company"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entity": {
|
|
3
|
+
"name": "Empresa",
|
|
4
|
+
"plural": "Empresas",
|
|
5
|
+
"description": "Gestiona empresas clientes y prospectos"
|
|
6
|
+
},
|
|
7
|
+
"fields": {
|
|
8
|
+
"name": {
|
|
9
|
+
"label": "Nombre de Empresa",
|
|
10
|
+
"description": "Nombre comercial de la empresa",
|
|
11
|
+
"placeholder": "Ingrese nombre de empresa..."
|
|
12
|
+
},
|
|
13
|
+
"legalName": {
|
|
14
|
+
"label": "Razón Social",
|
|
15
|
+
"description": "Nombre legal registrado",
|
|
16
|
+
"placeholder": "Ingrese razón social..."
|
|
17
|
+
},
|
|
18
|
+
"taxId": {
|
|
19
|
+
"label": "RFC/NIT",
|
|
20
|
+
"description": "Número de identificación fiscal",
|
|
21
|
+
"placeholder": "Ingrese RFC/NIT..."
|
|
22
|
+
},
|
|
23
|
+
"website": {
|
|
24
|
+
"label": "Sitio Web",
|
|
25
|
+
"description": "URL del sitio web",
|
|
26
|
+
"placeholder": "https://ejemplo.com"
|
|
27
|
+
},
|
|
28
|
+
"email": {
|
|
29
|
+
"label": "Email",
|
|
30
|
+
"description": "Email general de la empresa",
|
|
31
|
+
"placeholder": "info@empresa.com"
|
|
32
|
+
},
|
|
33
|
+
"phone": {
|
|
34
|
+
"label": "Teléfono",
|
|
35
|
+
"description": "Teléfono principal",
|
|
36
|
+
"placeholder": "Ingrese teléfono..."
|
|
37
|
+
},
|
|
38
|
+
"industry": {
|
|
39
|
+
"label": "Industria",
|
|
40
|
+
"description": "Sector industrial",
|
|
41
|
+
"placeholder": "Ingrese industria..."
|
|
42
|
+
},
|
|
43
|
+
"type": {
|
|
44
|
+
"label": "Tipo de Empresa",
|
|
45
|
+
"description": "Tipo de relación empresarial",
|
|
46
|
+
"placeholder": "Seleccionar tipo..."
|
|
47
|
+
},
|
|
48
|
+
"size": {
|
|
49
|
+
"label": "Tamaño",
|
|
50
|
+
"description": "Tamaño por empleados",
|
|
51
|
+
"placeholder": "Seleccionar tamaño..."
|
|
52
|
+
},
|
|
53
|
+
"annualRevenue": {
|
|
54
|
+
"label": "Ingresos Anuales",
|
|
55
|
+
"description": "Ingresos anuales en moneda",
|
|
56
|
+
"placeholder": "0.00"
|
|
57
|
+
},
|
|
58
|
+
"address": {
|
|
59
|
+
"label": "Dirección",
|
|
60
|
+
"description": "Dirección postal",
|
|
61
|
+
"placeholder": "Ingrese dirección..."
|
|
62
|
+
},
|
|
63
|
+
"city": {
|
|
64
|
+
"label": "Ciudad",
|
|
65
|
+
"description": "Ciudad",
|
|
66
|
+
"placeholder": "Ingrese ciudad..."
|
|
67
|
+
},
|
|
68
|
+
"state": {
|
|
69
|
+
"label": "Estado",
|
|
70
|
+
"description": "Estado o provincia",
|
|
71
|
+
"placeholder": "Ingrese estado..."
|
|
72
|
+
},
|
|
73
|
+
"country": {
|
|
74
|
+
"label": "País",
|
|
75
|
+
"description": "País",
|
|
76
|
+
"placeholder": "MX"
|
|
77
|
+
},
|
|
78
|
+
"postalCode": {
|
|
79
|
+
"label": "Código Postal",
|
|
80
|
+
"description": "Código postal",
|
|
81
|
+
"placeholder": "Ingrese código postal..."
|
|
82
|
+
},
|
|
83
|
+
"logo": {
|
|
84
|
+
"label": "Logo",
|
|
85
|
+
"description": "URL del logo",
|
|
86
|
+
"placeholder": "https://ejemplo.com/logo.png"
|
|
87
|
+
},
|
|
88
|
+
"rating": {
|
|
89
|
+
"label": "Calificación",
|
|
90
|
+
"description": "Calificación de ventas",
|
|
91
|
+
"placeholder": "Seleccionar calificación..."
|
|
92
|
+
},
|
|
93
|
+
"assignedTo": {
|
|
94
|
+
"label": "Asignado a",
|
|
95
|
+
"description": "Gerente de cuenta asignado",
|
|
96
|
+
"placeholder": "Seleccionar usuario..."
|
|
97
|
+
},
|
|
98
|
+
"createdAt": {
|
|
99
|
+
"label": "Creado el",
|
|
100
|
+
"description": "Cuándo se creó la empresa"
|
|
101
|
+
},
|
|
102
|
+
"updatedAt": {
|
|
103
|
+
"label": "Actualizado el",
|
|
104
|
+
"description": "Cuándo se actualizó por última vez"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"options": {
|
|
108
|
+
"type": {
|
|
109
|
+
"prospect": "Prospecto",
|
|
110
|
+
"customer": "Cliente",
|
|
111
|
+
"partner": "Socio",
|
|
112
|
+
"competitor": "Competidor",
|
|
113
|
+
"vendor": "Proveedor",
|
|
114
|
+
"other": "Otro"
|
|
115
|
+
},
|
|
116
|
+
"size": {
|
|
117
|
+
"1-10": "1-10 empleados",
|
|
118
|
+
"11-50": "11-50 empleados",
|
|
119
|
+
"51-200": "51-200 empleados",
|
|
120
|
+
"201-500": "201-500 empleados",
|
|
121
|
+
"500+": "500+ empleados"
|
|
122
|
+
},
|
|
123
|
+
"rating": {
|
|
124
|
+
"hot": "Caliente",
|
|
125
|
+
"warm": "Tibio",
|
|
126
|
+
"cold": "Frío"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"actions": {
|
|
130
|
+
"create": "Crear Empresa",
|
|
131
|
+
"edit": "Editar Empresa",
|
|
132
|
+
"delete": "Eliminar Empresa",
|
|
133
|
+
"view": "Ver Empresa",
|
|
134
|
+
"list": "Listar Empresas",
|
|
135
|
+
"search": "Buscar Empresas",
|
|
136
|
+
"export": "Exportar Empresas",
|
|
137
|
+
"import": "Importar Empresas"
|
|
138
|
+
},
|
|
139
|
+
"messages": {
|
|
140
|
+
"created": "Empresa creada exitosamente",
|
|
141
|
+
"updated": "Empresa actualizada exitosamente",
|
|
142
|
+
"deleted": "Empresa eliminada exitosamente",
|
|
143
|
+
"notFound": "Empresa no encontrada",
|
|
144
|
+
"error": "Ocurrió un error al procesar la empresa"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Companies Table Migration
|
|
3
|
+
-- CRM theme: Customer and prospect companies
|
|
4
|
+
-- Updated with team support and RLS
|
|
5
|
+
-- ============================================================================
|
|
6
|
+
|
|
7
|
+
-- ============================================
|
|
8
|
+
-- ENUM TYPES
|
|
9
|
+
-- ============================================
|
|
10
|
+
DO $$ BEGIN
|
|
11
|
+
CREATE TYPE company_type AS ENUM ('prospect', 'customer', 'partner', 'competitor');
|
|
12
|
+
EXCEPTION
|
|
13
|
+
WHEN duplicate_object THEN null;
|
|
14
|
+
END $$;
|
|
15
|
+
|
|
16
|
+
DO $$ BEGIN
|
|
17
|
+
CREATE TYPE company_size AS ENUM ('1-10', '11-50', '51-200', '201-500', '500+');
|
|
18
|
+
EXCEPTION
|
|
19
|
+
WHEN duplicate_object THEN null;
|
|
20
|
+
END $$;
|
|
21
|
+
|
|
22
|
+
DO $$ BEGIN
|
|
23
|
+
CREATE TYPE company_rating AS ENUM ('hot', 'warm', 'cold');
|
|
24
|
+
EXCEPTION
|
|
25
|
+
WHEN duplicate_object THEN null;
|
|
26
|
+
END $$;
|
|
27
|
+
|
|
28
|
+
-- ============================================
|
|
29
|
+
-- TABLE
|
|
30
|
+
-- ============================================
|
|
31
|
+
CREATE TABLE IF NOT EXISTS "companies" (
|
|
32
|
+
"id" TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
33
|
+
|
|
34
|
+
-- Company information
|
|
35
|
+
"name" VARCHAR(255) NOT NULL,
|
|
36
|
+
"legalName" VARCHAR(255),
|
|
37
|
+
"taxId" VARCHAR(50),
|
|
38
|
+
"website" VARCHAR(500),
|
|
39
|
+
"email" VARCHAR(255),
|
|
40
|
+
"phone" VARCHAR(50),
|
|
41
|
+
|
|
42
|
+
-- Classification
|
|
43
|
+
"industry" VARCHAR(100),
|
|
44
|
+
"type" company_type DEFAULT 'prospect',
|
|
45
|
+
"size" company_size,
|
|
46
|
+
"annualRevenue" DECIMAL(15,2),
|
|
47
|
+
"rating" company_rating DEFAULT 'warm',
|
|
48
|
+
|
|
49
|
+
-- Address
|
|
50
|
+
"address" VARCHAR(500),
|
|
51
|
+
"city" VARCHAR(100),
|
|
52
|
+
"state" VARCHAR(100),
|
|
53
|
+
"country" VARCHAR(100),
|
|
54
|
+
"postalCode" VARCHAR(20),
|
|
55
|
+
|
|
56
|
+
-- Social
|
|
57
|
+
"logo" VARCHAR(500),
|
|
58
|
+
"linkedin" VARCHAR(500),
|
|
59
|
+
"facebook" VARCHAR(500),
|
|
60
|
+
"twitter" VARCHAR(100),
|
|
61
|
+
|
|
62
|
+
-- Assignment
|
|
63
|
+
"assignedTo" TEXT REFERENCES "users"("id") ON DELETE SET NULL,
|
|
64
|
+
|
|
65
|
+
-- Ownership
|
|
66
|
+
"userId" TEXT NOT NULL REFERENCES "users"("id") ON DELETE CASCADE,
|
|
67
|
+
"teamId" TEXT NOT NULL REFERENCES "teams"("id") ON DELETE CASCADE,
|
|
68
|
+
|
|
69
|
+
-- Timestamps
|
|
70
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
71
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
-- ============================================
|
|
75
|
+
-- INDEXES
|
|
76
|
+
-- ============================================
|
|
77
|
+
CREATE INDEX IF NOT EXISTS "companies_teamId_idx" ON "companies" ("teamId");
|
|
78
|
+
CREATE INDEX IF NOT EXISTS "companies_userId_idx" ON "companies" ("userId");
|
|
79
|
+
CREATE INDEX IF NOT EXISTS "companies_name_idx" ON "companies" ("name");
|
|
80
|
+
CREATE INDEX IF NOT EXISTS "companies_type_idx" ON "companies" ("type");
|
|
81
|
+
CREATE INDEX IF NOT EXISTS "companies_industry_idx" ON "companies" ("industry");
|
|
82
|
+
CREATE INDEX IF NOT EXISTS "companies_rating_idx" ON "companies" ("rating");
|
|
83
|
+
CREATE INDEX IF NOT EXISTS "companies_assignedTo_idx" ON "companies" ("assignedTo");
|
|
84
|
+
CREATE INDEX IF NOT EXISTS "companies_createdAt_idx" ON "companies" ("createdAt" DESC);
|
|
85
|
+
|
|
86
|
+
-- ============================================
|
|
87
|
+
-- RLS
|
|
88
|
+
-- ============================================
|
|
89
|
+
ALTER TABLE "companies" ENABLE ROW LEVEL SECURITY;
|
|
90
|
+
|
|
91
|
+
-- Drop existing policies
|
|
92
|
+
DROP POLICY IF EXISTS "companies_select_policy" ON "companies";
|
|
93
|
+
DROP POLICY IF EXISTS "companies_insert_policy" ON "companies";
|
|
94
|
+
DROP POLICY IF EXISTS "companies_update_policy" ON "companies";
|
|
95
|
+
DROP POLICY IF EXISTS "companies_delete_policy" ON "companies";
|
|
96
|
+
|
|
97
|
+
-- Policy: Team members can view companies
|
|
98
|
+
CREATE POLICY "companies_select_policy" ON "companies"
|
|
99
|
+
FOR SELECT
|
|
100
|
+
USING (
|
|
101
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
102
|
+
OR public.is_superadmin()
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
-- Policy: Team members can create companies
|
|
106
|
+
CREATE POLICY "companies_insert_policy" ON "companies"
|
|
107
|
+
FOR INSERT
|
|
108
|
+
WITH CHECK (
|
|
109
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
-- Policy: Team members can update companies
|
|
113
|
+
CREATE POLICY "companies_update_policy" ON "companies"
|
|
114
|
+
FOR UPDATE
|
|
115
|
+
USING (
|
|
116
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
117
|
+
OR public.is_superadmin()
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
-- Policy: Team members can delete companies
|
|
121
|
+
CREATE POLICY "companies_delete_policy" ON "companies"
|
|
122
|
+
FOR DELETE
|
|
123
|
+
USING (
|
|
124
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
125
|
+
OR public.is_superadmin()
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
-- ============================================
|
|
129
|
+
-- TRIGGER updatedAt
|
|
130
|
+
-- ============================================
|
|
131
|
+
CREATE OR REPLACE FUNCTION update_companies_updated_at()
|
|
132
|
+
RETURNS TRIGGER AS $$
|
|
133
|
+
BEGIN
|
|
134
|
+
NEW."updatedAt" = NOW();
|
|
135
|
+
RETURN NEW;
|
|
136
|
+
END;
|
|
137
|
+
$$ LANGUAGE plpgsql;
|
|
138
|
+
|
|
139
|
+
DROP TRIGGER IF EXISTS companies_updated_at_trigger ON "companies";
|
|
140
|
+
CREATE TRIGGER companies_updated_at_trigger
|
|
141
|
+
BEFORE UPDATE ON "companies"
|
|
142
|
+
FOR EACH ROW
|
|
143
|
+
EXECUTE FUNCTION update_companies_updated_at();
|
|
144
|
+
|
|
145
|
+
-- ============================================
|
|
146
|
+
-- COMMENTS
|
|
147
|
+
-- ============================================
|
|
148
|
+
COMMENT ON TABLE "companies" IS 'Customer and prospect companies';
|
|
149
|
+
COMMENT ON COLUMN "companies"."type" IS 'Company relationship type';
|
|
150
|
+
COMMENT ON COLUMN "companies"."rating" IS 'Company temperature rating';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
-- Migration: 002_companies_metas.sql
|
|
2
|
+
-- Description: Contacts metas (table, indexes, RLS)
|
|
3
|
+
-- Date: 2025-09-27
|
|
4
|
+
|
|
5
|
+
-- ============================================
|
|
6
|
+
-- TABLE
|
|
7
|
+
-- ============================================
|
|
8
|
+
-- No DROP needed - removed automatically by parent table CASCADE
|
|
9
|
+
CREATE TABLE IF NOT EXISTS public."companies_metas" (
|
|
10
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
11
|
+
"entityId" TEXT NOT NULL REFERENCES public."companies"(id) ON DELETE CASCADE,
|
|
12
|
+
"metaKey" TEXT NOT NULL,
|
|
13
|
+
"metaValue" JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
14
|
+
"dataType" TEXT DEFAULT 'json',
|
|
15
|
+
"isPublic" BOOLEAN NOT NULL DEFAULT false,
|
|
16
|
+
"isSearchable" BOOLEAN NOT NULL DEFAULT false,
|
|
17
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
18
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
19
|
+
CONSTRAINT companies_metas_unique_key UNIQUE ("entityId", "metaKey")
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
COMMENT ON TABLE public."companies_metas" IS 'Contacts metadata table - stores additional key-value pairs for companies';
|
|
23
|
+
COMMENT ON COLUMN public."companies_metas"."entityId" IS 'Generic foreign key to parent companie entity';
|
|
24
|
+
COMMENT ON COLUMN public."companies_metas"."metaKey" IS 'Metadata key name';
|
|
25
|
+
COMMENT ON COLUMN public."companies_metas"."metaValue" IS 'Metadata value as JSONB';
|
|
26
|
+
COMMENT ON COLUMN public."companies_metas"."dataType" IS 'Type hint for the value: json, string, number, boolean';
|
|
27
|
+
COMMENT ON COLUMN public."companies_metas"."isPublic" IS 'Whether this metadata is publicly readable';
|
|
28
|
+
COMMENT ON COLUMN public."companies_metas"."isSearchable" IS 'Whether this metadata is searchable';
|
|
29
|
+
|
|
30
|
+
-- ============================================
|
|
31
|
+
-- TRIGGER updatedAt (uses Better Auth function)
|
|
32
|
+
-- ============================================
|
|
33
|
+
DROP TRIGGER IF EXISTS companies_metas_set_updated_at ON public."companies_metas";
|
|
34
|
+
CREATE TRIGGER companies_metas_set_updated_at
|
|
35
|
+
BEFORE UPDATE ON public."companies_metas"
|
|
36
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
37
|
+
|
|
38
|
+
-- ============================================
|
|
39
|
+
-- INDEXES
|
|
40
|
+
-- ============================================
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_companies_metas_entity_id ON public."companies_metas"("entityId");
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_companies_metas_key ON public."companies_metas"("metaKey");
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_companies_metas_composite ON public."companies_metas"("entityId", "metaKey", "isPublic");
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_companies_metas_is_public ON public."companies_metas"("isPublic") WHERE "isPublic" = true;
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_companies_metas_is_searchable ON public."companies_metas"("isSearchable") WHERE "isSearchable" = true;
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_companies_metas_searchable_key ON public."companies_metas"("metaKey") WHERE "isSearchable" = true;
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_companies_metas_value_gin ON public."companies_metas" USING GIN ("metaValue");
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_companies_metas_value_ops ON public."companies_metas" USING GIN ("metaValue" jsonb_path_ops);
|
|
49
|
+
|
|
50
|
+
-- ============================================
|
|
51
|
+
-- RLS
|
|
52
|
+
-- ============================================
|
|
53
|
+
ALTER TABLE public."companies_metas" ENABLE ROW LEVEL SECURITY;
|
|
54
|
+
|
|
55
|
+
-- Cleanup existing policies
|
|
56
|
+
DROP POLICY IF EXISTS "Users can view companie metas" ON public."companies_metas";
|
|
57
|
+
DROP POLICY IF EXISTS "Users can create companie metas" ON public."companies_metas";
|
|
58
|
+
DROP POLICY IF EXISTS "Users can update companie metas" ON public."companies_metas";
|
|
59
|
+
DROP POLICY IF EXISTS "Users can delete companie metas" ON public."companies_metas";
|
|
60
|
+
|
|
61
|
+
-- ============================
|
|
62
|
+
-- AUTHENTICATED USER POLICIES
|
|
63
|
+
-- ============================
|
|
64
|
+
-- Inherit permissions from parent entity
|
|
65
|
+
CREATE POLICY "Users can view companie metas"
|
|
66
|
+
ON public."companies_metas"
|
|
67
|
+
FOR SELECT TO authenticated
|
|
68
|
+
USING (
|
|
69
|
+
EXISTS (
|
|
70
|
+
SELECT 1 FROM public."companies" c
|
|
71
|
+
WHERE c.id = "entityId"
|
|
72
|
+
AND c."userId" = public.get_auth_user_id()
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
CREATE POLICY "Users can create companie metas"
|
|
77
|
+
ON public."companies_metas"
|
|
78
|
+
FOR INSERT TO authenticated
|
|
79
|
+
WITH CHECK (
|
|
80
|
+
EXISTS (
|
|
81
|
+
SELECT 1 FROM public."companies" c
|
|
82
|
+
WHERE c.id = "entityId"
|
|
83
|
+
AND c."userId" = public.get_auth_user_id()
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
CREATE POLICY "Users can update companie metas"
|
|
88
|
+
ON public."companies_metas"
|
|
89
|
+
FOR UPDATE TO authenticated
|
|
90
|
+
USING (
|
|
91
|
+
EXISTS (
|
|
92
|
+
SELECT 1 FROM public."companies" c
|
|
93
|
+
WHERE c.id = "entityId"
|
|
94
|
+
AND c."userId" = public.get_auth_user_id()
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
WITH CHECK (
|
|
98
|
+
EXISTS (
|
|
99
|
+
SELECT 1 FROM public."companies" c
|
|
100
|
+
WHERE c.id = "entityId"
|
|
101
|
+
AND c."userId" = public.get_auth_user_id()
|
|
102
|
+
)
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
CREATE POLICY "Users can delete companie metas"
|
|
106
|
+
ON public."companies_metas"
|
|
107
|
+
FOR DELETE TO authenticated
|
|
108
|
+
USING (
|
|
109
|
+
EXISTS (
|
|
110
|
+
SELECT 1 FROM public."companies" c
|
|
111
|
+
WHERE c.id = "entityId"
|
|
112
|
+
AND c."userId" = public.get_auth_user_id()
|
|
113
|
+
)
|
|
114
|
+
);
|