Packages
phoenix_kit
1.7.169
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
221 Versions
Jump to
Current section
221 Versions
Compare versions
7
files changed
+230
additions
-7
deletions
unknownCHANGELOG.md
File is too large to be displayed (100 KB limit).
| @@ -1,6 +1,6 @@ | |
| 1 1 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/BeamLabEU/phoenix_kit">>}]}. |
| 2 2 | {<<"name">>,<<"phoenix_kit">>}. |
| 3 | - {<<"version">>,<<"1.7.168">>}. |
| 3 | + {<<"version">>,<<"1.7.169">>}. |
| 4 4 | {<<"description">>, |
| 5 5 | <<65,32,102,111,117,110,100,97,116,105,111,110,32,102,111,114,32,98,117,105, |
| 6 6 | 108,100,105,110,103,32,69,108,105,120,105,114,32,80,104,111,101,110,105, |
| @@ -499,6 +499,7 @@ | |
| 499 499 | <<"lib/phoenix_kit/migrations/postgres/v25.ex">>, |
| 500 500 | <<"lib/phoenix_kit/migrations/postgres/v115.ex">>, |
| 501 501 | <<"lib/phoenix_kit/migrations/postgres/v55.ex">>, |
| 502 | + <<"lib/phoenix_kit/migrations/postgres/v138.ex">>, |
| 502 503 | <<"lib/phoenix_kit/migrations/postgres/v78.ex">>, |
| 503 504 | <<"lib/phoenix_kit/migrations/postgres/v27.ex">>, |
| 504 505 | <<"lib/phoenix_kit/migrations/postgres/v08.ex">>, |
unknownlib/modules/storage/storage.ex
File is too large to be displayed (100 KB limit).
| @@ -529,7 +529,19 @@ defmodule PhoenixKit.Migrations.Postgres do | |
| 529 529 | - Replaces unique index with partial index (slug-mode only, WHERE slug IS NOT NULL) |
| 530 530 | - Adds unique index on `(group_uuid, post_date, post_time)` for timestamp-mode posts |
| 531 531 | |
| 532 | - ### V136 - Staff employment history ⚡ LATEST |
| 532 | + ### V138 - CRM v1 interaction tracker ⚡ LATEST |
| 533 | + - Adds five `phoenix_kit_crm_*` tables for the CRM module's first data model: |
| 534 | + `contacts` (profile + **optional** `user_uuid` login link, partial-unique so |
| 535 | + it's 1:1 only among linked rows), `companies`, `company_memberships` (M:N |
| 536 | + contact↔company with free-form `role_in_company` + `department` + `is_primary` |
| 537 | + on the edge), `interactions` (logged interaction: type/when/body/subject |
| 538 | + contact/owner user), and `interaction_parties` (flat resolvable "who was |
| 539 | + involved": `raw_name` always kept, `contact_uuid`/`staff_person_uuid` resolve |
| 540 | + when matched under an exclusive-arc CHECK, `party_snapshot` JSONB freezes the |
| 541 | + party's profile as-of-then). `staff_person_uuid` is a soft ref (no FK) so the |
| 542 | + optional staff module stays optional. |
| 543 | + |
| 544 | + ### V136 - Staff employment history |
| 533 545 | - Adds `phoenix_kit_staff_employments` — a per-person history of employment |
| 534 546 | spans (employment type, translatable `job_title`, org placement via |
| 535 547 | `primary_department_uuid` + a `primary_team_uuid` snapshot, date range with |
| @@ -1172,7 +1184,7 @@ defmodule PhoenixKit.Migrations.Postgres do | |
| 1172 1184 | use Ecto.Migration |
| 1173 1185 | |
| 1174 1186 | @initial_version 1 |
| 1175 | - @current_version 137 |
| 1187 | + @current_version 138 |
| 1176 1188 | @default_prefix "public" |
| 1177 1189 | |
| 1178 1190 | @doc false |
| @@ -0,0 +1,204 @@ | |
| 1 | + defmodule PhoenixKit.Migrations.Postgres.V138 do |
| 2 | + @moduledoc """ |
| 3 | + V138: CRM v1 — interaction tracker (contacts, companies, interactions). |
| 4 | + |
| 5 | + Five tables for the `phoenix_kit_crm` plugin's first real data model: a |
| 6 | + Contact (cloned in spirit from staff `Person`, but the user link is |
| 7 | + **optional**), a Company, the contact↔company membership (carrying free-form |
| 8 | + role + department on the edge), and the interaction log (entries + their |
| 9 | + resolvable "involved parties" with an as-of-then profile snapshot). |
| 10 | + |
| 11 | + ## phoenix_kit_crm_contacts |
| 12 | + A CRM contact (client/customer). Unlike staff `Person`, `user_uuid` is |
| 13 | + **nullable** — most contacts never log in. A partial unique index keeps it |
| 14 | + 1:1 (one contact per user) only among the rows that *are* linked. |
| 15 | + |
| 16 | + ## phoenix_kit_crm_companies |
| 17 | + A company/organization record (its own data; not a login user). |
| 18 | + |
| 19 | + ## phoenix_kit_crm_company_memberships |
| 20 | + Contact↔company link, many-to-many, with free-form `role_in_company` and |
| 21 | + `department` on the edge + an `is_primary` flag (drives the headline display |
| 22 | + and the interaction snapshot). |
| 23 | + |
| 24 | + ## phoenix_kit_crm_interactions |
| 25 | + A logged interaction ("client called, discussed X"): type, when, body, |
| 26 | + the subject contact, and the staff user who logged it. |
| 27 | + |
| 28 | + ## phoenix_kit_crm_interaction_parties |
| 29 | + Flat, resolvable "who was involved" list per interaction. `raw_name` is |
| 30 | + always kept; `contact_uuid` / `staff_person_uuid` resolve to records when |
| 31 | + matched (exclusive-arc: at most one set). `party_snapshot` freezes the |
| 32 | + party's profile as it was at log time. `staff_person_uuid` is a **soft ref** |
| 33 | + (no FK) so the optional staff module stays optional. |
| 34 | + |
| 35 | + All operations are idempotent. |
| 36 | + """ |
| 37 | + |
| 38 | + use Ecto.Migration |
| 39 | + |
| 40 | + def up(opts) do |
| 41 | + prefix = Map.get(opts, :prefix, "public") |
| 42 | + p = prefix_str(prefix) |
| 43 | + |
| 44 | + # ── Contacts ────────────────────────────────────────────────────── |
| 45 | + execute(""" |
| 46 | + CREATE TABLE IF NOT EXISTS #{p}phoenix_kit_crm_contacts ( |
| 47 | + uuid UUID PRIMARY KEY DEFAULT uuid_generate_v7(), |
| 48 | + name VARCHAR(255), |
| 49 | + status VARCHAR(50) NOT NULL DEFAULT 'active', |
| 50 | + email VARCHAR(255), |
| 51 | + phone VARCHAR(50), |
| 52 | + notes TEXT, |
| 53 | + user_uuid UUID REFERENCES #{p}phoenix_kit_users(uuid) ON DELETE SET NULL, |
| 54 | + metadata JSONB NOT NULL DEFAULT '{}', |
| 55 | + inserted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 56 | + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 57 | + ) |
| 58 | + """) |
| 59 | + |
| 60 | + # One contact per linked user (only among rows that are actually linked). |
| 61 | + execute(""" |
| 62 | + CREATE UNIQUE INDEX IF NOT EXISTS idx_crm_contacts_user_uuid |
| 63 | + ON #{p}phoenix_kit_crm_contacts (user_uuid) |
| 64 | + WHERE user_uuid IS NOT NULL |
| 65 | + """) |
| 66 | + |
| 67 | + execute(""" |
| 68 | + CREATE INDEX IF NOT EXISTS idx_crm_contacts_status |
| 69 | + ON #{p}phoenix_kit_crm_contacts (status) |
| 70 | + """) |
| 71 | + |
| 72 | + # ── Companies ───────────────────────────────────────────────────── |
| 73 | + execute(""" |
| 74 | + CREATE TABLE IF NOT EXISTS #{p}phoenix_kit_crm_companies ( |
| 75 | + uuid UUID PRIMARY KEY DEFAULT uuid_generate_v7(), |
| 76 | + name VARCHAR(255), |
| 77 | + status VARCHAR(50) NOT NULL DEFAULT 'active', |
| 78 | + website VARCHAR(255), |
| 79 | + email VARCHAR(255), |
| 80 | + phone VARCHAR(50), |
| 81 | + address TEXT, |
| 82 | + industry VARCHAR(255), |
| 83 | + notes TEXT, |
| 84 | + metadata JSONB NOT NULL DEFAULT '{}', |
| 85 | + inserted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 86 | + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 87 | + ) |
| 88 | + """) |
| 89 | + |
| 90 | + execute(""" |
| 91 | + CREATE INDEX IF NOT EXISTS idx_crm_companies_status |
| 92 | + ON #{p}phoenix_kit_crm_companies (status) |
| 93 | + """) |
| 94 | + |
| 95 | + # ── Contact ↔ Company memberships ───────────────────────────────── |
| 96 | + execute(""" |
| 97 | + CREATE TABLE IF NOT EXISTS #{p}phoenix_kit_crm_company_memberships ( |
| 98 | + uuid UUID PRIMARY KEY DEFAULT uuid_generate_v7(), |
| 99 | + contact_uuid UUID NOT NULL REFERENCES #{p}phoenix_kit_crm_contacts(uuid) ON DELETE CASCADE, |
| 100 | + company_uuid UUID NOT NULL REFERENCES #{p}phoenix_kit_crm_companies(uuid) ON DELETE CASCADE, |
| 101 | + role_in_company VARCHAR(255), |
| 102 | + department VARCHAR(255), |
| 103 | + is_primary BOOLEAN NOT NULL DEFAULT false, |
| 104 | + position INTEGER NOT NULL DEFAULT 0, |
| 105 | + inserted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 106 | + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 107 | + CONSTRAINT phoenix_kit_crm_company_memberships_uniq UNIQUE (contact_uuid, company_uuid) |
| 108 | + ) |
| 109 | + """) |
| 110 | + |
| 111 | + execute(""" |
| 112 | + CREATE INDEX IF NOT EXISTS idx_crm_memberships_contact |
| 113 | + ON #{p}phoenix_kit_crm_company_memberships (contact_uuid) |
| 114 | + """) |
| 115 | + |
| 116 | + execute(""" |
| 117 | + CREATE INDEX IF NOT EXISTS idx_crm_memberships_company |
| 118 | + ON #{p}phoenix_kit_crm_company_memberships (company_uuid) |
| 119 | + """) |
| 120 | + |
| 121 | + # ── Interactions ────────────────────────────────────────────────── |
| 122 | + execute(""" |
| 123 | + CREATE TABLE IF NOT EXISTS #{p}phoenix_kit_crm_interactions ( |
| 124 | + uuid UUID PRIMARY KEY DEFAULT uuid_generate_v7(), |
| 125 | + contact_uuid UUID NOT NULL REFERENCES #{p}phoenix_kit_crm_contacts(uuid) ON DELETE CASCADE, |
| 126 | + interaction_type VARCHAR(50) NOT NULL DEFAULT 'note', |
| 127 | + occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 128 | + subject VARCHAR(255), |
| 129 | + body TEXT, |
| 130 | + owner_user_uuid UUID REFERENCES #{p}phoenix_kit_users(uuid) ON DELETE SET NULL, |
| 131 | + metadata JSONB NOT NULL DEFAULT '{}', |
| 132 | + inserted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 133 | + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 134 | + ) |
| 135 | + """) |
| 136 | + |
| 137 | + execute(""" |
| 138 | + CREATE INDEX IF NOT EXISTS idx_crm_interactions_contact |
| 139 | + ON #{p}phoenix_kit_crm_interactions (contact_uuid, occurred_at DESC) |
| 140 | + """) |
| 141 | + |
| 142 | + # ── Interaction parties (resolvable mentions) ───────────────────── |
| 143 | + # raw_name always kept; contact_uuid / staff_person_uuid resolve when |
| 144 | + # matched (at most one). staff_person_uuid is a SOFT ref (no FK) so the |
| 145 | + # optional staff module stays optional. |
| 146 | + execute(""" |
| 147 | + CREATE TABLE IF NOT EXISTS #{p}phoenix_kit_crm_interaction_parties ( |
| 148 | + uuid UUID PRIMARY KEY DEFAULT uuid_generate_v7(), |
| 149 | + interaction_uuid UUID NOT NULL REFERENCES #{p}phoenix_kit_crm_interactions(uuid) ON DELETE CASCADE, |
| 150 | + raw_name VARCHAR(255) NOT NULL, |
| 151 | + contact_uuid UUID REFERENCES #{p}phoenix_kit_crm_contacts(uuid) ON DELETE SET NULL, |
| 152 | + staff_person_uuid UUID, |
| 153 | + party_snapshot JSONB NOT NULL DEFAULT '{}', |
| 154 | + position INTEGER NOT NULL DEFAULT 0, |
| 155 | + inserted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 156 | + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 157 | + CONSTRAINT phoenix_kit_crm_party_exclusive_arc |
| 158 | + CHECK (NOT (contact_uuid IS NOT NULL AND staff_person_uuid IS NOT NULL)) |
| 159 | + ) |
| 160 | + """) |
| 161 | + |
| 162 | + execute(""" |
| 163 | + CREATE INDEX IF NOT EXISTS idx_crm_parties_interaction |
| 164 | + ON #{p}phoenix_kit_crm_interaction_parties (interaction_uuid) |
| 165 | + """) |
| 166 | + |
| 167 | + execute(""" |
| 168 | + CREATE INDEX IF NOT EXISTS idx_crm_parties_contact |
| 169 | + ON #{p}phoenix_kit_crm_interaction_parties (contact_uuid) |
| 170 | + WHERE contact_uuid IS NOT NULL |
| 171 | + """) |
| 172 | + |
| 173 | + execute(""" |
| 174 | + CREATE INDEX IF NOT EXISTS idx_crm_parties_staff_person |
| 175 | + ON #{p}phoenix_kit_crm_interaction_parties (staff_person_uuid) |
| 176 | + WHERE staff_person_uuid IS NOT NULL |
| 177 | + """) |
| 178 | + |
| 179 | + execute("COMMENT ON TABLE #{p}phoenix_kit IS '138'") |
| 180 | + end |
| 181 | + |
| 182 | + @doc """ |
| 183 | + Rolls V138 back by dropping the five CRM v1 tables in reverse dependency |
| 184 | + order. |
| 185 | + |
| 186 | + **Lossy rollback:** all contacts, companies, memberships, interactions, and |
| 187 | + party rows are lost. Back up before rolling back in production. |
| 188 | + """ |
| 189 | + def down(opts) do |
| 190 | + prefix = Map.get(opts, :prefix, "public") |
| 191 | + p = prefix_str(prefix) |
| 192 | + |
| 193 | + execute("DROP TABLE IF EXISTS #{p}phoenix_kit_crm_interaction_parties CASCADE") |
| 194 | + execute("DROP TABLE IF EXISTS #{p}phoenix_kit_crm_interactions CASCADE") |
| 195 | + execute("DROP TABLE IF EXISTS #{p}phoenix_kit_crm_company_memberships CASCADE") |
| 196 | + execute("DROP TABLE IF EXISTS #{p}phoenix_kit_crm_companies CASCADE") |
| 197 | + execute("DROP TABLE IF EXISTS #{p}phoenix_kit_crm_contacts CASCADE") |
| 198 | + |
| 199 | + execute("COMMENT ON TABLE #{p}phoenix_kit IS '137'") |
| 200 | + end |
| 201 | + |
| 202 | + defp prefix_str("public"), do: "public." |
| 203 | + defp prefix_str(prefix), do: "#{prefix}." |
| 204 | + end |
Loading more files…