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

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 + contactcompany 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…