realtimex-crm 0.9.10 → 0.11.2
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/dist/assets/{DealList-Bfe4r2OW.js → DealList-B_Xs90US.js} +3 -3
- package/dist/assets/{DealList-Bfe4r2OW.js.map → DealList-B_Xs90US.js.map} +1 -1
- package/dist/assets/{index-Dat6OHMH.js → index-B-3Bhhk5.js} +63 -63
- package/dist/assets/{index-Dat6OHMH.js.map → index-B-3Bhhk5.js.map} +1 -1
- package/dist/index.html +1 -1
- package/dist/stats.html +1 -1
- package/package.json +1 -1
- package/src/components/atomic-crm/companies/CompanyAside.tsx +12 -6
- package/src/components/atomic-crm/companies/CompanyMergeButton.tsx +237 -0
- package/src/components/atomic-crm/deals/stages.test.ts +54 -0
- package/src/components/atomic-crm/deals/stages.ts +3 -1
- package/src/components/atomic-crm/providers/commons/mergeCompanies.ts +119 -0
- package/src/components/atomic-crm/providers/fakerest/dataProvider.ts +4 -0
- package/src/components/atomic-crm/providers/supabase/dataProvider.ts +13 -0
- package/supabase/functions/_shared/db.ts +23 -0
- package/supabase/functions/api-v1-companies/index.ts +42 -2
- package/supabase/functions/api-v1-contacts/index.ts +38 -2
- package/supabase/functions/mergeCompanies/index.ts +163 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
2
|
+
import { createClient } from "jsr:@supabase/supabase-js@2";
|
|
3
|
+
import type { Selectable } from "https://esm.sh/kysely@0.27.2";
|
|
4
|
+
import { db, type CompaniesTable, CompiledQuery } from "../_shared/db.ts";
|
|
5
|
+
import { corsHeaders, createErrorResponse } from "../_shared/utils.ts";
|
|
6
|
+
|
|
7
|
+
type Company = Selectable<CompaniesTable>;
|
|
8
|
+
|
|
9
|
+
// Helper function to merge arrays and remove duplicates
|
|
10
|
+
function mergeArraysUnique<T>(arr1: T[], arr2: T[]): T[] {
|
|
11
|
+
return [...new Set([...arr1, ...arr2])];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function mergeCompanyData(winner: Company, loser: Company) {
|
|
15
|
+
// Merge context_links arrays
|
|
16
|
+
const mergedContextLinks = mergeArraysUnique(
|
|
17
|
+
winner.context_links || [],
|
|
18
|
+
loser.context_links || [],
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const selectedLogo =
|
|
22
|
+
winner.logo && winner.logo.src ? winner.logo : loser.logo;
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
logo: selectedLogo ? (JSON.stringify(selectedLogo) as any) : null,
|
|
26
|
+
sector: winner.sector ?? loser.sector,
|
|
27
|
+
size: winner.size ?? loser.size,
|
|
28
|
+
linkedin_url: winner.linkedin_url || loser.linkedin_url,
|
|
29
|
+
website: winner.website || loser.website,
|
|
30
|
+
phone_number: winner.phone_number ?? loser.phone_number,
|
|
31
|
+
address: winner.address ?? loser.address,
|
|
32
|
+
zipcode: winner.zipcode ?? loser.zipcode,
|
|
33
|
+
city: winner.city ?? loser.city,
|
|
34
|
+
stateAbbr: winner.stateAbbr ?? loser.stateAbbr,
|
|
35
|
+
country: winner.country ?? loser.country,
|
|
36
|
+
description: winner.description ?? loser.description,
|
|
37
|
+
revenue: winner.revenue ?? loser.revenue,
|
|
38
|
+
tax_identifier: winner.tax_identifier ?? loser.tax_identifier,
|
|
39
|
+
sales_id: winner.sales_id ?? loser.sales_id,
|
|
40
|
+
context_links: mergedContextLinks.length > 0
|
|
41
|
+
? (JSON.stringify(mergedContextLinks) as any)
|
|
42
|
+
: null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function mergeCompanies(
|
|
47
|
+
loserId: number,
|
|
48
|
+
winnerId: number,
|
|
49
|
+
userId: string,
|
|
50
|
+
) {
|
|
51
|
+
try {
|
|
52
|
+
return await db.transaction().execute(async (trx) => {
|
|
53
|
+
// Enable RLS by switching to authenticated role and setting user context
|
|
54
|
+
await trx.executeQuery(CompiledQuery.raw("SET LOCAL ROLE authenticated"));
|
|
55
|
+
await trx.executeQuery(
|
|
56
|
+
CompiledQuery.raw(
|
|
57
|
+
`SELECT set_config('request.jwt.claim.sub', '${userId}', true)`,
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// 1. Fetch both companies
|
|
62
|
+
const [winner, loser] = await Promise.all([
|
|
63
|
+
trx
|
|
64
|
+
.selectFrom("companies")
|
|
65
|
+
.selectAll()
|
|
66
|
+
.where("id", "=", winnerId)
|
|
67
|
+
.executeTakeFirstOrThrow(),
|
|
68
|
+
trx
|
|
69
|
+
.selectFrom("companies")
|
|
70
|
+
.selectAll()
|
|
71
|
+
.where("id", "=", loserId)
|
|
72
|
+
.executeTakeFirstOrThrow(),
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
// 2. Reassign contacts from loser to winner
|
|
76
|
+
await trx
|
|
77
|
+
.updateTable("contacts")
|
|
78
|
+
.set({ company_id: winnerId })
|
|
79
|
+
.where("company_id", "=", loserId)
|
|
80
|
+
.execute();
|
|
81
|
+
|
|
82
|
+
// 3. Reassign deals from loser to winner
|
|
83
|
+
await trx
|
|
84
|
+
.updateTable("deals")
|
|
85
|
+
.set({ company_id: winnerId })
|
|
86
|
+
.where("company_id", "=", loserId)
|
|
87
|
+
.execute();
|
|
88
|
+
|
|
89
|
+
// 4. Merge and update winner company
|
|
90
|
+
const mergedData = mergeCompanyData(winner as Company, loser as Company);
|
|
91
|
+
await trx
|
|
92
|
+
.updateTable("companies")
|
|
93
|
+
.set(mergedData)
|
|
94
|
+
.where("id", "=", winnerId)
|
|
95
|
+
.execute();
|
|
96
|
+
|
|
97
|
+
// 5. Delete loser company
|
|
98
|
+
await trx.deleteFrom("companies").where("id", "=", loserId).execute();
|
|
99
|
+
|
|
100
|
+
return { success: true, winnerId };
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error("Transaction failed:", error);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Deno.serve(async (req: Request) => {
|
|
109
|
+
// Handle CORS preflight
|
|
110
|
+
if (req.method === "OPTIONS") {
|
|
111
|
+
return new Response(null, {
|
|
112
|
+
status: 204,
|
|
113
|
+
headers: corsHeaders,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Authenticate user via Supabase client
|
|
118
|
+
const authHeader = req.headers.get("Authorization");
|
|
119
|
+
if (!authHeader) {
|
|
120
|
+
return createErrorResponse(401, "Missing Authorization header");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const supabaseClient = createClient(
|
|
124
|
+
Deno.env.get("SUPABASE_URL") ?? "",
|
|
125
|
+
Deno.env.get("SUPABASE_ANON_KEY") ?? "",
|
|
126
|
+
{ global: { headers: { Authorization: authHeader } } },
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const {
|
|
130
|
+
data: { user },
|
|
131
|
+
error: authError,
|
|
132
|
+
} = await supabaseClient.auth.getUser();
|
|
133
|
+
if (!user || authError) {
|
|
134
|
+
return createErrorResponse(401, "Unauthorized");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle POST request
|
|
138
|
+
if (req.method === "POST") {
|
|
139
|
+
try {
|
|
140
|
+
const { loserId, winnerId } = await req.json();
|
|
141
|
+
|
|
142
|
+
if (!loserId || !winnerId) {
|
|
143
|
+
return createErrorResponse(400, "Missing loserId or winnerId");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const result = await mergeCompanies(loserId, winnerId, user.id);
|
|
147
|
+
|
|
148
|
+
return new Response(JSON.stringify(result), {
|
|
149
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("Merge failed:", error);
|
|
153
|
+
return createErrorResponse(
|
|
154
|
+
500,
|
|
155
|
+
`Failed to merge companies: ${
|
|
156
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
157
|
+
}`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return createErrorResponse(405, "Method Not Allowed");
|
|
163
|
+
});
|