Commit iniziale di eminuxCRM: CRM Rails con pipeline, campagne email e Docker.
Include autenticazione, progetti isolati, mail marketing HTML con SMTP, test A/B e editor WYSIWYG. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
class CreateSimplecrmSchema < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :users do |t|
|
||||
t.string :email, null: false
|
||||
t.string :password_digest, null: false
|
||||
t.string :first_name, null: false
|
||||
t.string :last_name, null: false
|
||||
t.string :role, null: false, default: "user"
|
||||
t.boolean :active, null: false, default: true
|
||||
t.string :password_reset_token
|
||||
t.datetime :password_reset_sent_at
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :users, :email, unique: true
|
||||
add_index :users, :password_reset_token, unique: true
|
||||
add_index :users, :role
|
||||
|
||||
create_table :products do |t|
|
||||
t.string :name, null: false
|
||||
t.string :code, null: false
|
||||
t.boolean :active, null: false, default: true
|
||||
t.integer :position, null: false, default: 0
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :products, :code, unique: true
|
||||
add_index :products, :position
|
||||
|
||||
create_table :organizations do |t|
|
||||
t.string :name, null: false
|
||||
t.string :legal_name
|
||||
t.string :organization_type, null: false, default: "altro"
|
||||
t.string :sport
|
||||
t.string :country, null: false, default: "Italia"
|
||||
t.string :region
|
||||
t.string :province
|
||||
t.string :city
|
||||
t.string :address
|
||||
t.string :website
|
||||
t.string :phone
|
||||
t.string :email
|
||||
t.string :vat_number
|
||||
t.text :notes
|
||||
t.string :status, null: false, default: "prospect"
|
||||
t.string :lead_source
|
||||
t.references :assigned_user, foreign_key: { to_table: :users }
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :organizations, :status
|
||||
add_index :organizations, :lead_source
|
||||
add_index :organizations, :organization_type
|
||||
add_index :organizations, :email
|
||||
add_index :organizations, :name
|
||||
add_index :organizations, :city
|
||||
add_index :organizations, :website
|
||||
add_index :organizations, :sport
|
||||
add_index :organizations, :country
|
||||
add_index :organizations, :region
|
||||
|
||||
create_table :contacts do |t|
|
||||
t.references :organization, null: false, foreign_key: true
|
||||
t.string :first_name, null: false
|
||||
t.string :last_name, null: false
|
||||
t.string :role
|
||||
t.string :email
|
||||
t.string :phone
|
||||
t.string :mobile
|
||||
t.string :preferred_contact_method, default: "email"
|
||||
t.text :notes
|
||||
t.boolean :primary_contact, null: false, default: false
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :contacts, :email
|
||||
add_index :contacts, :phone
|
||||
add_index :contacts, :primary_contact
|
||||
add_index :contacts, [:last_name, :first_name]
|
||||
|
||||
create_table :opportunities do |t|
|
||||
t.references :organization, null: false, foreign_key: true
|
||||
t.string :name, null: false
|
||||
t.string :pipeline_stage, null: false, default: "to_contact"
|
||||
t.decimal :estimated_value, precision: 12, scale: 2
|
||||
t.integer :probability, default: 0
|
||||
t.date :expected_close_date
|
||||
t.string :product
|
||||
t.references :assigned_user, foreign_key: { to_table: :users }
|
||||
t.string :lost_reason
|
||||
t.text :notes
|
||||
t.datetime :won_at
|
||||
t.datetime :lost_at
|
||||
t.datetime :stage_changed_at
|
||||
t.datetime :first_contacted_at
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :opportunities, :pipeline_stage
|
||||
add_index :opportunities, :lost_reason
|
||||
add_index :opportunities, :product
|
||||
add_index :opportunities, :expected_close_date
|
||||
add_index :opportunities, :stage_changed_at
|
||||
|
||||
create_table :activities do |t|
|
||||
t.string :activity_type, null: false
|
||||
t.string :subject, null: false
|
||||
t.text :description
|
||||
t.datetime :happened_at, null: false
|
||||
t.references :user, null: false, foreign_key: true
|
||||
t.references :organization, null: false, foreign_key: true
|
||||
t.references :contact, foreign_key: true
|
||||
t.references :opportunity, foreign_key: true
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :activities, :activity_type
|
||||
add_index :activities, :happened_at
|
||||
add_index :activities, [:organization_id, :happened_at]
|
||||
|
||||
create_table :tasks do |t|
|
||||
t.string :title, null: false
|
||||
t.text :description
|
||||
t.references :organization, null: false, foreign_key: true
|
||||
t.references :contact, foreign_key: true
|
||||
t.references :opportunity, foreign_key: true
|
||||
t.references :assigned_user, foreign_key: { to_table: :users }
|
||||
t.datetime :due_at, null: false
|
||||
t.datetime :completed_at
|
||||
t.string :priority, null: false, default: "normal"
|
||||
t.string :task_type, null: false, default: "generic"
|
||||
t.string :status, null: false, default: "pending"
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :tasks, :due_at
|
||||
add_index :tasks, :status
|
||||
add_index :tasks, :priority
|
||||
add_index :tasks, :task_type
|
||||
add_index :tasks, [:status, :due_at]
|
||||
|
||||
create_table :sales_goals do |t|
|
||||
t.string :name, null: false
|
||||
t.string :metric, null: false
|
||||
t.decimal :target_value, precision: 12, scale: 2, null: false
|
||||
t.date :start_date, null: false
|
||||
t.date :end_date, null: false
|
||||
t.boolean :active, null: false, default: true
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :sales_goals, :metric
|
||||
add_index :sales_goals, [:start_date, :end_date]
|
||||
add_index :sales_goals, :active
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
class AddProjects < ActiveRecord::Migration[8.1]
|
||||
def up
|
||||
create_table :projects do |t|
|
||||
t.string :name, null: false
|
||||
t.string :code, null: false
|
||||
t.text :description
|
||||
t.boolean :active, null: false, default: true
|
||||
t.integer :position, null: false, default: 0
|
||||
t.string :color, default: "#0f172a"
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :projects, :code, unique: true
|
||||
add_index :projects, :active
|
||||
add_index :projects, :position
|
||||
|
||||
create_table :user_projects do |t|
|
||||
t.references :user, null: false, foreign_key: true
|
||||
t.references :project, null: false, foreign_key: true
|
||||
t.boolean :enabled, null: false, default: true
|
||||
t.timestamps
|
||||
end
|
||||
add_index :user_projects, [:user_id, :project_id], unique: true
|
||||
add_index :user_projects, :enabled
|
||||
|
||||
create_table :organization_projects do |t|
|
||||
t.references :organization, null: false, foreign_key: true
|
||||
t.references :project, null: false, foreign_key: true
|
||||
t.timestamps
|
||||
end
|
||||
add_index :organization_projects, [:organization_id, :project_id], unique: true, name: "idx_org_projects_unique"
|
||||
|
||||
add_reference :opportunities, :project, foreign_key: true
|
||||
add_reference :sales_goals, :project, foreign_key: true
|
||||
|
||||
say_with_time "Backfill default projects" do
|
||||
now = quote(Time.current)
|
||||
execute <<~SQL.squish
|
||||
INSERT INTO projects (name, code, description, active, position, color, created_at, updated_at) VALUES
|
||||
('MatchLiveTV', 'matchlivetv', 'Streaming e diritti sportivi', TRUE, 0, '#0f172a', #{now}, #{now}),
|
||||
('RiskMeter', 'riskmeter', 'Risk management', TRUE, 1, '#0369a1', #{now}, #{now}),
|
||||
('Cardoo', 'cardoo', 'Cardoo', TRUE, 2, '#047857', #{now}, #{now})
|
||||
SQL
|
||||
|
||||
project_id = select_value("SELECT id FROM projects WHERE code = 'matchlivetv'")
|
||||
|
||||
execute <<~SQL.squish
|
||||
INSERT INTO organization_projects (organization_id, project_id, created_at, updated_at)
|
||||
SELECT id, #{project_id}, #{now}, #{now} FROM organizations
|
||||
ON CONFLICT (organization_id, project_id) DO NOTHING
|
||||
SQL
|
||||
|
||||
execute "UPDATE opportunities SET project_id = #{project_id} WHERE project_id IS NULL"
|
||||
execute "UPDATE sales_goals SET project_id = #{project_id} WHERE project_id IS NULL"
|
||||
|
||||
execute <<~SQL.squish
|
||||
INSERT INTO user_projects (user_id, project_id, enabled, created_at, updated_at)
|
||||
SELECT u.id, p.id, TRUE, #{now}, #{now}
|
||||
FROM users u
|
||||
CROSS JOIN projects p
|
||||
WHERE u.role = 'user'
|
||||
ON CONFLICT (user_id, project_id) DO NOTHING
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
remove_reference :sales_goals, :project, foreign_key: true
|
||||
remove_reference :opportunities, :project, foreign_key: true
|
||||
drop_table :organization_projects
|
||||
drop_table :user_projects
|
||||
drop_table :projects
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class AllowNullActivityUserId < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
change_column_null :activities, :user_id, true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
class AddCampaignFieldsToOrganizationsAndOpportunities < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
change_table :organizations, bulk: true do |t|
|
||||
t.integer :list_position
|
||||
t.string :team_gender
|
||||
t.string :streaming_status
|
||||
t.text :commercial_fit
|
||||
t.string :source_url
|
||||
t.date :verified_at
|
||||
end
|
||||
add_index :organizations, :list_position
|
||||
add_index :organizations, :team_gender
|
||||
add_index :organizations, :streaming_status
|
||||
|
||||
change_table :opportunities, bulk: true do |t|
|
||||
t.string :ab_variant
|
||||
t.string :send_status
|
||||
t.date :sent_on
|
||||
t.string :outcome
|
||||
t.boolean :demo_trial, default: false, null: false
|
||||
t.boolean :converted, default: false, null: false
|
||||
end
|
||||
add_index :opportunities, :ab_variant
|
||||
add_index :opportunities, :send_status
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
# This migration comes from active_storage (originally 20170806125915)
|
||||
class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
# Use Active Record's configured type for primary and foreign keys
|
||||
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
||||
|
||||
create_table :active_storage_blobs, id: primary_key_type do |t|
|
||||
t.string :key, null: false
|
||||
t.string :filename, null: false
|
||||
t.string :content_type
|
||||
t.text :metadata
|
||||
t.string :service_name, null: false
|
||||
t.bigint :byte_size, null: false
|
||||
t.string :checksum
|
||||
|
||||
if connection.supports_datetime_with_precision?
|
||||
t.datetime :created_at, precision: 6, null: false
|
||||
else
|
||||
t.datetime :created_at, null: false
|
||||
end
|
||||
|
||||
t.index [ :key ], unique: true
|
||||
end
|
||||
|
||||
create_table :active_storage_attachments, id: primary_key_type do |t|
|
||||
t.string :name, null: false
|
||||
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
|
||||
t.references :blob, null: false, type: foreign_key_type
|
||||
|
||||
if connection.supports_datetime_with_precision?
|
||||
t.datetime :created_at, precision: 6, null: false
|
||||
else
|
||||
t.datetime :created_at, null: false
|
||||
end
|
||||
|
||||
t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
|
||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
||||
end
|
||||
|
||||
create_table :active_storage_variant_records, id: primary_key_type do |t|
|
||||
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
|
||||
t.string :variation_digest, null: false
|
||||
|
||||
t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
|
||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def primary_and_foreign_key_types
|
||||
config = Rails.configuration.generators
|
||||
setting = config.options[config.orm][:primary_key_type]
|
||||
primary_key_type = setting || :primary_key
|
||||
foreign_key_type = setting || :bigint
|
||||
[ primary_key_type, foreign_key_type ]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
class CreateMailMarketing < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :mail_identities do |t|
|
||||
t.string :name, null: false
|
||||
t.string :from_name, null: false
|
||||
t.string :from_email, null: false
|
||||
t.string :reply_to
|
||||
t.string :smtp_host, null: false
|
||||
t.integer :smtp_port, null: false, default: 587
|
||||
t.string :smtp_username
|
||||
t.text :smtp_password
|
||||
t.string :smtp_authentication, default: "plain", null: false
|
||||
t.string :encryption, default: "starttls", null: false
|
||||
t.boolean :verify_ssl, default: true, null: false
|
||||
t.boolean :active, default: true, null: false
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :mail_identities, :from_email
|
||||
add_index :mail_identities, :active
|
||||
|
||||
create_table :mail_templates do |t|
|
||||
t.bigint :project_id
|
||||
t.string :name, null: false
|
||||
t.string :subject, null: false
|
||||
t.text :body_html, null: false
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :mail_templates, :project_id
|
||||
add_index :mail_templates, :name
|
||||
|
||||
create_table :mailings do |t|
|
||||
t.bigint :project_id, null: false
|
||||
t.bigint :mail_identity_id, null: false
|
||||
t.bigint :mail_template_id
|
||||
t.string :name, null: false
|
||||
t.string :subject, null: false
|
||||
t.text :body_html, null: false
|
||||
t.string :audience, default: "to_send", null: false
|
||||
t.string :status, default: "draft", null: false
|
||||
t.integer :interval_seconds, default: 0, null: false
|
||||
t.datetime :queued_at
|
||||
t.datetime :completed_at
|
||||
t.bigint :created_by_id
|
||||
t.bigint :updated_by_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :mailings, :project_id
|
||||
add_index :mailings, :mail_identity_id
|
||||
add_index :mailings, :status
|
||||
|
||||
create_table :mailing_recipients do |t|
|
||||
t.bigint :mailing_id, null: false
|
||||
t.bigint :organization_id, null: false
|
||||
t.bigint :contact_id
|
||||
t.string :email
|
||||
t.string :status, default: "pending", null: false
|
||||
t.string :skip_reason
|
||||
t.text :error_message
|
||||
t.string :rendered_subject
|
||||
t.datetime :sent_at
|
||||
t.timestamps
|
||||
end
|
||||
add_index :mailing_recipients, %i[mailing_id organization_id], unique: true, name: "idx_mailing_recipients_unique"
|
||||
add_index :mailing_recipients, :status
|
||||
add_index :mailing_recipients, :email
|
||||
|
||||
add_foreign_key :mail_templates, :projects
|
||||
add_foreign_key :mailings, :projects
|
||||
add_foreign_key :mailings, :mail_identities
|
||||
add_foreign_key :mailings, :mail_templates
|
||||
add_foreign_key :mailing_recipients, :mailings
|
||||
add_foreign_key :mailing_recipients, :organizations
|
||||
add_foreign_key :mailing_recipients, :contacts
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
class AddAbTestToMailings < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
change_table :mailings do |t|
|
||||
t.boolean :ab_test, default: false, null: false
|
||||
t.string :ab_assignment, default: "from_record", null: false
|
||||
t.string :subject_b
|
||||
t.text :body_html_b
|
||||
t.bigint :mail_template_b_id
|
||||
end
|
||||
add_index :mailings, :ab_test
|
||||
add_foreign_key :mailings, :mail_templates, column: :mail_template_b_id
|
||||
|
||||
add_column :mailing_recipients, :ab_variant, :string
|
||||
add_index :mailing_recipients, :ab_variant
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user