@hasna/contacts 0.2.8 → 0.3.0
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/cli/index.js +902 -25
- package/dist/db/applications.d.ts +10 -0
- package/dist/db/applications.d.ts.map +1 -0
- package/dist/db/companies.d.ts +4 -0
- package/dist/db/companies.d.ts.map +1 -1
- package/dist/db/contact-tasks.d.ts +19 -0
- package/dist/db/contact-tasks.d.ts.map +1 -0
- package/dist/db/contacts.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/notes.d.ts +2 -1
- package/dist/db/notes.d.ts.map +1 -1
- package/dist/db/org-members.d.ts +9 -0
- package/dist/db/org-members.d.ts.map +1 -0
- package/dist/db/relationships.d.ts +11 -0
- package/dist/db/relationships.d.ts.map +1 -1
- package/dist/db/vendor-comms.d.ts +15 -0
- package/dist/db/vendor-comms.d.ts.map +1 -0
- package/dist/index.d.ts +17 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1134 -4
- package/dist/lib/google-contacts.test.d.ts +2 -0
- package/dist/lib/google-contacts.test.d.ts.map +1 -0
- package/dist/mcp/index.js +1416 -41
- package/dist/server/index.js +102 -4
- package/dist/types/index.d.ts +204 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -226,6 +226,85 @@ var MIGRATIONS = [
|
|
|
226
226
|
);
|
|
227
227
|
|
|
228
228
|
CREATE INDEX IF NOT EXISTS idx_contact_notes_contact ON contact_notes(contact_id);
|
|
229
|
+
`,
|
|
230
|
+
`
|
|
231
|
+
ALTER TABLE companies ADD COLUMN is_owned_entity INTEGER NOT NULL DEFAULT 0;
|
|
232
|
+
ALTER TABLE companies ADD COLUMN entity_type TEXT CHECK(entity_type IN ('operating','holding','dissolved','nonprofit','trust','branch','other'));
|
|
233
|
+
|
|
234
|
+
ALTER TABLE company_relationships ADD COLUMN start_date TEXT;
|
|
235
|
+
ALTER TABLE company_relationships ADD COLUMN end_date TEXT;
|
|
236
|
+
ALTER TABLE company_relationships ADD COLUMN is_primary INTEGER NOT NULL DEFAULT 0;
|
|
237
|
+
ALTER TABLE company_relationships ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','inactive','ended'));
|
|
238
|
+
|
|
239
|
+
CREATE TABLE IF NOT EXISTS org_members (
|
|
240
|
+
id TEXT PRIMARY KEY,
|
|
241
|
+
company_id TEXT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
242
|
+
contact_id TEXT NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
|
|
243
|
+
title TEXT,
|
|
244
|
+
specialization TEXT,
|
|
245
|
+
office_phone TEXT,
|
|
246
|
+
response_sla_hours INTEGER,
|
|
247
|
+
notes TEXT,
|
|
248
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
249
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
250
|
+
UNIQUE(company_id, contact_id)
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
CREATE TABLE IF NOT EXISTS vendor_communications (
|
|
254
|
+
id TEXT PRIMARY KEY,
|
|
255
|
+
company_id TEXT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
256
|
+
contact_id TEXT REFERENCES contacts(id) ON DELETE SET NULL,
|
|
257
|
+
comm_date TEXT NOT NULL,
|
|
258
|
+
type TEXT NOT NULL DEFAULT 'email' CHECK(type IN ('email','call','meeting','invoice_request','invoice_received','payment','dispute','other')),
|
|
259
|
+
direction TEXT NOT NULL DEFAULT 'outbound' CHECK(direction IN ('inbound','outbound')),
|
|
260
|
+
subject TEXT,
|
|
261
|
+
body TEXT,
|
|
262
|
+
status TEXT NOT NULL DEFAULT 'sent' CHECK(status IN ('sent','awaiting_response','responded','no_response','resolved')),
|
|
263
|
+
invoice_amount REAL,
|
|
264
|
+
invoice_currency TEXT,
|
|
265
|
+
invoice_ref TEXT,
|
|
266
|
+
follow_up_date TEXT,
|
|
267
|
+
follow_up_done INTEGER NOT NULL DEFAULT 0,
|
|
268
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
CREATE TABLE IF NOT EXISTS contact_tasks (
|
|
272
|
+
id TEXT PRIMARY KEY,
|
|
273
|
+
title TEXT NOT NULL,
|
|
274
|
+
description TEXT,
|
|
275
|
+
contact_id TEXT NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
|
|
276
|
+
assigned_by TEXT,
|
|
277
|
+
deadline TEXT,
|
|
278
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','awaiting_response','in_progress','completed','cancelled','escalated')),
|
|
279
|
+
priority TEXT NOT NULL DEFAULT 'medium' CHECK(priority IN ('low','medium','high','critical')),
|
|
280
|
+
entity_id TEXT REFERENCES companies(id) ON DELETE SET NULL,
|
|
281
|
+
linked_todos_task_id TEXT,
|
|
282
|
+
escalation_rules TEXT NOT NULL DEFAULT '[]',
|
|
283
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
284
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
CREATE TABLE IF NOT EXISTS applications (
|
|
288
|
+
id TEXT PRIMARY KEY,
|
|
289
|
+
program_name TEXT NOT NULL,
|
|
290
|
+
provider_company_id TEXT REFERENCES companies(id) ON DELETE SET NULL,
|
|
291
|
+
type TEXT NOT NULL DEFAULT 'other' CHECK(type IN ('ai_credits','grant','startup_program','visa','trademark','tax_filing','loan','other')),
|
|
292
|
+
value_usd REAL,
|
|
293
|
+
applicant_contact_id TEXT REFERENCES contacts(id) ON DELETE SET NULL,
|
|
294
|
+
primary_contact_id TEXT REFERENCES contacts(id) ON DELETE SET NULL,
|
|
295
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK(status IN ('draft','submitted','pending','approved','rejected','follow_up_needed','expired','cancelled')),
|
|
296
|
+
submitted_date TEXT,
|
|
297
|
+
decision_date TEXT,
|
|
298
|
+
follow_up_date TEXT,
|
|
299
|
+
notes TEXT,
|
|
300
|
+
method TEXT CHECK(method IN ('email','form','typeform','hubspot','manual','browser','feathery','other')),
|
|
301
|
+
form_url TEXT,
|
|
302
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
303
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
304
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
ALTER TABLE contact_notes ADD COLUMN company_id TEXT REFERENCES companies(id) ON DELETE SET NULL;
|
|
229
308
|
`
|
|
230
309
|
];
|
|
231
310
|
var _db = null;
|
|
@@ -380,7 +459,9 @@ function rowToCompany(row) {
|
|
|
380
459
|
...row,
|
|
381
460
|
custom_fields: JSON.parse(row.custom_fields || "{}"),
|
|
382
461
|
archived: !!row.archived,
|
|
383
|
-
project_id: row.project_id ?? null
|
|
462
|
+
project_id: row.project_id ?? null,
|
|
463
|
+
is_owned_entity: !!row.is_owned_entity,
|
|
464
|
+
entity_type: row.entity_type ?? null
|
|
384
465
|
};
|
|
385
466
|
}
|
|
386
467
|
function insertEmails(db, contactId, companyId, emails) {
|
|
@@ -836,7 +917,9 @@ function rowToCompany2(row) {
|
|
|
836
917
|
...row,
|
|
837
918
|
custom_fields: JSON.parse(row.custom_fields || "{}"),
|
|
838
919
|
archived: !!row.archived,
|
|
839
|
-
project_id: row.project_id ?? null
|
|
920
|
+
project_id: row.project_id ?? null,
|
|
921
|
+
is_owned_entity: !!row.is_owned_entity,
|
|
922
|
+
entity_type: row.entity_type ?? null
|
|
840
923
|
};
|
|
841
924
|
}
|
|
842
925
|
function insertEmails2(db, companyId, emails) {
|
|
@@ -900,8 +983,8 @@ function createCompany(input, db) {
|
|
|
900
983
|
const d = db || getDatabase();
|
|
901
984
|
const id = uuid();
|
|
902
985
|
const timestamp = now();
|
|
903
|
-
d.run(`INSERT INTO companies (id, name, domain, logo_url, description, industry, size, founded_year, notes, custom_fields, created_at, updated_at)
|
|
904
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
986
|
+
d.run(`INSERT INTO companies (id, name, domain, logo_url, description, industry, size, founded_year, notes, custom_fields, is_owned_entity, entity_type, created_at, updated_at)
|
|
987
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
905
988
|
id,
|
|
906
989
|
input.name,
|
|
907
990
|
input.domain ?? null,
|
|
@@ -912,6 +995,8 @@ function createCompany(input, db) {
|
|
|
912
995
|
input.founded_year ?? null,
|
|
913
996
|
input.notes ?? null,
|
|
914
997
|
JSON.stringify(input.custom_fields ?? {}),
|
|
998
|
+
input.is_owned_entity ? 1 : 0,
|
|
999
|
+
input.entity_type ?? null,
|
|
915
1000
|
timestamp,
|
|
916
1001
|
timestamp
|
|
917
1002
|
]);
|
|
@@ -948,6 +1033,7 @@ function listCompanies(opts = {}, db) {
|
|
|
948
1033
|
tag_id,
|
|
949
1034
|
project_id,
|
|
950
1035
|
archived = false,
|
|
1036
|
+
is_owned_entity,
|
|
951
1037
|
order_by = "name",
|
|
952
1038
|
order_dir = "asc"
|
|
953
1039
|
} = opts;
|
|
@@ -967,6 +1053,10 @@ function listCompanies(opts = {}, db) {
|
|
|
967
1053
|
conditions.push("EXISTS (SELECT 1 FROM company_tags ct WHERE ct.company_id = co.id AND ct.tag_id = ?)");
|
|
968
1054
|
params.push(tag_id);
|
|
969
1055
|
}
|
|
1056
|
+
if (is_owned_entity !== undefined) {
|
|
1057
|
+
conditions.push("co.is_owned_entity = ?");
|
|
1058
|
+
params.push(is_owned_entity ? 1 : 0);
|
|
1059
|
+
}
|
|
970
1060
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
971
1061
|
const validOrderBy = ["name", "created_at", "updated_at"].includes(order_by) ? order_by : "name";
|
|
972
1062
|
const validOrderDir = order_dir === "desc" ? "DESC" : "ASC";
|
|
@@ -1022,6 +1112,14 @@ function updateCompany(id, input, db) {
|
|
|
1022
1112
|
setClauses.push("project_id = ?");
|
|
1023
1113
|
params.push(input.project_id);
|
|
1024
1114
|
}
|
|
1115
|
+
if (input.is_owned_entity !== undefined) {
|
|
1116
|
+
setClauses.push("is_owned_entity = ?");
|
|
1117
|
+
params.push(input.is_owned_entity ? 1 : 0);
|
|
1118
|
+
}
|
|
1119
|
+
if ("entity_type" in input && input.entity_type !== undefined) {
|
|
1120
|
+
setClauses.push("entity_type = ?");
|
|
1121
|
+
params.push(input.entity_type);
|
|
1122
|
+
}
|
|
1025
1123
|
params.push(id);
|
|
1026
1124
|
d.run(`UPDATE companies SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
1027
1125
|
logActivity(d, { company_id: id, action: "company.updated", details: `Updated company: ${existing.name}` });
|
|
@@ -1082,6 +1180,9 @@ function unarchiveCompany(id, db) {
|
|
|
1082
1180
|
const updated = d.query(`SELECT * FROM companies WHERE id = ?`).get(id);
|
|
1083
1181
|
return loadCompanyDetails(d, rowToCompany2(updated));
|
|
1084
1182
|
}
|
|
1183
|
+
function listOwnedEntities(db) {
|
|
1184
|
+
return listCompanies({ is_owned_entity: true }, db);
|
|
1185
|
+
}
|
|
1085
1186
|
// src/db/tags.ts
|
|
1086
1187
|
function rowToTag2(row) {
|
|
1087
1188
|
return { ...row };
|
|
@@ -1243,6 +1344,635 @@ function deleteRelationship(id, db) {
|
|
|
1243
1344
|
const d = db || getDatabase();
|
|
1244
1345
|
d.run(`DELETE FROM contact_relationships WHERE id = ?`, [id]);
|
|
1245
1346
|
}
|
|
1347
|
+
function rowToCompanyRelationship(row) {
|
|
1348
|
+
return {
|
|
1349
|
+
...row,
|
|
1350
|
+
relationship_type: row.relationship_type,
|
|
1351
|
+
start_date: row.start_date ?? null,
|
|
1352
|
+
end_date: row.end_date ?? null,
|
|
1353
|
+
is_primary: !!row.is_primary,
|
|
1354
|
+
status: row.status ?? "active"
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
function createCompanyRelationship(input, db) {
|
|
1358
|
+
const d = db || getDatabase();
|
|
1359
|
+
const contact = d.query(`SELECT id FROM contacts WHERE id = ?`).get(input.contact_id);
|
|
1360
|
+
if (!contact)
|
|
1361
|
+
throw new ContactNotFoundError(input.contact_id);
|
|
1362
|
+
const company = d.query(`SELECT id FROM companies WHERE id = ?`).get(input.company_id);
|
|
1363
|
+
if (!company)
|
|
1364
|
+
throw new Error(`Company ${input.company_id} not found`);
|
|
1365
|
+
const id = uuid();
|
|
1366
|
+
d.run(`INSERT INTO company_relationships (id, contact_id, company_id, relationship_type, notes, start_date, end_date, is_primary, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1367
|
+
id,
|
|
1368
|
+
input.contact_id,
|
|
1369
|
+
input.company_id,
|
|
1370
|
+
input.relationship_type,
|
|
1371
|
+
input.notes ?? null,
|
|
1372
|
+
input.start_date ?? null,
|
|
1373
|
+
input.end_date ?? null,
|
|
1374
|
+
input.is_primary ? 1 : 0,
|
|
1375
|
+
input.status ?? "active"
|
|
1376
|
+
]);
|
|
1377
|
+
return rowToCompanyRelationship(d.query(`SELECT * FROM company_relationships WHERE id = ?`).get(id));
|
|
1378
|
+
}
|
|
1379
|
+
function listCompanyRelationships(opts = {}, db) {
|
|
1380
|
+
const d = db || getDatabase();
|
|
1381
|
+
const conditions = [];
|
|
1382
|
+
const params = [];
|
|
1383
|
+
if (opts.contact_id) {
|
|
1384
|
+
conditions.push("contact_id = ?");
|
|
1385
|
+
params.push(opts.contact_id);
|
|
1386
|
+
}
|
|
1387
|
+
if (opts.company_id) {
|
|
1388
|
+
conditions.push("company_id = ?");
|
|
1389
|
+
params.push(opts.company_id);
|
|
1390
|
+
}
|
|
1391
|
+
if (opts.relationship_type) {
|
|
1392
|
+
conditions.push("relationship_type = ?");
|
|
1393
|
+
params.push(opts.relationship_type);
|
|
1394
|
+
}
|
|
1395
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1396
|
+
const rows = d.query(`SELECT * FROM company_relationships ${where} ORDER BY created_at DESC`).all(...params);
|
|
1397
|
+
return rows.map(rowToCompanyRelationship);
|
|
1398
|
+
}
|
|
1399
|
+
function deleteCompanyRelationship(id, db) {
|
|
1400
|
+
const d = db || getDatabase();
|
|
1401
|
+
d.run(`DELETE FROM company_relationships WHERE id = ?`, [id]);
|
|
1402
|
+
}
|
|
1403
|
+
function getEntityTeam(companyId, db) {
|
|
1404
|
+
const d = db || getDatabase();
|
|
1405
|
+
const rows = d.query(`SELECT id, contact_id, relationship_type, is_primary, status, start_date, end_date, notes
|
|
1406
|
+
FROM company_relationships WHERE company_id = ? ORDER BY is_primary DESC, created_at ASC`).all(companyId);
|
|
1407
|
+
return rows.map((r) => ({
|
|
1408
|
+
contact_id: r.contact_id,
|
|
1409
|
+
relationship_id: r.id,
|
|
1410
|
+
relationship_type: r.relationship_type,
|
|
1411
|
+
is_primary: !!r.is_primary,
|
|
1412
|
+
status: r.status,
|
|
1413
|
+
start_date: r.start_date,
|
|
1414
|
+
end_date: r.end_date,
|
|
1415
|
+
notes: r.notes
|
|
1416
|
+
}));
|
|
1417
|
+
}
|
|
1418
|
+
// src/db/org-members.ts
|
|
1419
|
+
function rowToOrgMember(row) {
|
|
1420
|
+
return {
|
|
1421
|
+
id: row.id,
|
|
1422
|
+
company_id: row.company_id,
|
|
1423
|
+
contact_id: row.contact_id,
|
|
1424
|
+
title: row.title,
|
|
1425
|
+
specialization: row.specialization,
|
|
1426
|
+
office_phone: row.office_phone,
|
|
1427
|
+
response_sla_hours: row.response_sla_hours,
|
|
1428
|
+
notes: row.notes,
|
|
1429
|
+
created_at: row.created_at,
|
|
1430
|
+
updated_at: row.updated_at
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function addOrgMember(input, db) {
|
|
1434
|
+
const d = db || getDatabase();
|
|
1435
|
+
const id = uuid();
|
|
1436
|
+
const timestamp = now();
|
|
1437
|
+
d.run(`INSERT INTO org_members (id, company_id, contact_id, title, specialization, office_phone, response_sla_hours, notes, created_at, updated_at)
|
|
1438
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1439
|
+
id,
|
|
1440
|
+
input.company_id,
|
|
1441
|
+
input.contact_id,
|
|
1442
|
+
input.title ?? null,
|
|
1443
|
+
input.specialization ?? null,
|
|
1444
|
+
input.office_phone ?? null,
|
|
1445
|
+
input.response_sla_hours ?? null,
|
|
1446
|
+
input.notes ?? null,
|
|
1447
|
+
timestamp,
|
|
1448
|
+
timestamp
|
|
1449
|
+
]);
|
|
1450
|
+
return rowToOrgMember(d.query(`SELECT * FROM org_members WHERE id = ?`).get(id));
|
|
1451
|
+
}
|
|
1452
|
+
function getOrgMember(id, db) {
|
|
1453
|
+
const d = db || getDatabase();
|
|
1454
|
+
const row = d.query(`SELECT * FROM org_members WHERE id = ?`).get(id);
|
|
1455
|
+
return row ? rowToOrgMember(row) : null;
|
|
1456
|
+
}
|
|
1457
|
+
function listOrgMembers(companyId, db) {
|
|
1458
|
+
const d = db || getDatabase();
|
|
1459
|
+
const rows = d.query(`SELECT * FROM org_members WHERE company_id = ? ORDER BY created_at ASC`).all(companyId);
|
|
1460
|
+
return rows.map(rowToOrgMember);
|
|
1461
|
+
}
|
|
1462
|
+
function updateOrgMember(id, input, db) {
|
|
1463
|
+
const d = db || getDatabase();
|
|
1464
|
+
const setClauses = ["updated_at = ?"];
|
|
1465
|
+
const params = [now()];
|
|
1466
|
+
if ("title" in input) {
|
|
1467
|
+
setClauses.push("title = ?");
|
|
1468
|
+
params.push(input.title ?? null);
|
|
1469
|
+
}
|
|
1470
|
+
if ("specialization" in input) {
|
|
1471
|
+
setClauses.push("specialization = ?");
|
|
1472
|
+
params.push(input.specialization ?? null);
|
|
1473
|
+
}
|
|
1474
|
+
if ("office_phone" in input) {
|
|
1475
|
+
setClauses.push("office_phone = ?");
|
|
1476
|
+
params.push(input.office_phone ?? null);
|
|
1477
|
+
}
|
|
1478
|
+
if ("response_sla_hours" in input) {
|
|
1479
|
+
setClauses.push("response_sla_hours = ?");
|
|
1480
|
+
params.push(input.response_sla_hours ?? null);
|
|
1481
|
+
}
|
|
1482
|
+
if ("notes" in input) {
|
|
1483
|
+
setClauses.push("notes = ?");
|
|
1484
|
+
params.push(input.notes ?? null);
|
|
1485
|
+
}
|
|
1486
|
+
params.push(id);
|
|
1487
|
+
d.run(`UPDATE org_members SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
1488
|
+
return rowToOrgMember(d.query(`SELECT * FROM org_members WHERE id = ?`).get(id));
|
|
1489
|
+
}
|
|
1490
|
+
function removeOrgMember(id, db) {
|
|
1491
|
+
const d = db || getDatabase();
|
|
1492
|
+
d.run(`DELETE FROM org_members WHERE id = ?`, [id]);
|
|
1493
|
+
}
|
|
1494
|
+
function listOrgMembersForContact(contactId, db) {
|
|
1495
|
+
const d = db || getDatabase();
|
|
1496
|
+
const rows = d.query(`SELECT * FROM org_members WHERE contact_id = ? ORDER BY created_at ASC`).all(contactId);
|
|
1497
|
+
return rows.map(rowToOrgMember);
|
|
1498
|
+
}
|
|
1499
|
+
// src/db/vendor-comms.ts
|
|
1500
|
+
function rowToVendorComm(row) {
|
|
1501
|
+
return {
|
|
1502
|
+
id: row.id,
|
|
1503
|
+
company_id: row.company_id,
|
|
1504
|
+
contact_id: row.contact_id,
|
|
1505
|
+
comm_date: row.comm_date,
|
|
1506
|
+
type: row.type,
|
|
1507
|
+
direction: row.direction,
|
|
1508
|
+
subject: row.subject,
|
|
1509
|
+
body: row.body,
|
|
1510
|
+
status: row.status,
|
|
1511
|
+
invoice_amount: row.invoice_amount,
|
|
1512
|
+
invoice_currency: row.invoice_currency,
|
|
1513
|
+
invoice_ref: row.invoice_ref,
|
|
1514
|
+
follow_up_date: row.follow_up_date,
|
|
1515
|
+
follow_up_done: !!row.follow_up_done,
|
|
1516
|
+
created_at: row.created_at
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
function logVendorCommunication(input, db) {
|
|
1520
|
+
const d = db || getDatabase();
|
|
1521
|
+
const id = uuid();
|
|
1522
|
+
d.run(`INSERT INTO vendor_communications
|
|
1523
|
+
(id, company_id, contact_id, comm_date, type, direction, subject, body, status,
|
|
1524
|
+
invoice_amount, invoice_currency, invoice_ref, follow_up_date, follow_up_done)
|
|
1525
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1526
|
+
id,
|
|
1527
|
+
input.company_id,
|
|
1528
|
+
input.contact_id ?? null,
|
|
1529
|
+
input.comm_date,
|
|
1530
|
+
input.type ?? "email",
|
|
1531
|
+
input.direction ?? "outbound",
|
|
1532
|
+
input.subject ?? null,
|
|
1533
|
+
input.body ?? null,
|
|
1534
|
+
input.status ?? "sent",
|
|
1535
|
+
input.invoice_amount ?? null,
|
|
1536
|
+
input.invoice_currency ?? null,
|
|
1537
|
+
input.invoice_ref ?? null,
|
|
1538
|
+
input.follow_up_date ?? null,
|
|
1539
|
+
input.follow_up_done ? 1 : 0
|
|
1540
|
+
]);
|
|
1541
|
+
return rowToVendorComm(d.query(`SELECT * FROM vendor_communications WHERE id = ?`).get(id));
|
|
1542
|
+
}
|
|
1543
|
+
function listVendorCommunications(companyId, opts = {}, db) {
|
|
1544
|
+
const d = db || getDatabase();
|
|
1545
|
+
const conditions = ["company_id = ?"];
|
|
1546
|
+
const params = [companyId];
|
|
1547
|
+
if (opts.type) {
|
|
1548
|
+
conditions.push("type = ?");
|
|
1549
|
+
params.push(opts.type);
|
|
1550
|
+
}
|
|
1551
|
+
if (opts.status) {
|
|
1552
|
+
conditions.push("status = ?");
|
|
1553
|
+
params.push(opts.status);
|
|
1554
|
+
}
|
|
1555
|
+
if (opts.direction) {
|
|
1556
|
+
conditions.push("direction = ?");
|
|
1557
|
+
params.push(opts.direction);
|
|
1558
|
+
}
|
|
1559
|
+
const where = conditions.join(" AND ");
|
|
1560
|
+
const rows = d.query(`SELECT * FROM vendor_communications WHERE ${where} ORDER BY comm_date DESC`).all(...params);
|
|
1561
|
+
return rows.map(rowToVendorComm);
|
|
1562
|
+
}
|
|
1563
|
+
function updateVendorCommunication(id, input, db) {
|
|
1564
|
+
const d = db || getDatabase();
|
|
1565
|
+
const setClauses = [];
|
|
1566
|
+
const params = [];
|
|
1567
|
+
if ("contact_id" in input) {
|
|
1568
|
+
setClauses.push("contact_id = ?");
|
|
1569
|
+
params.push(input.contact_id ?? null);
|
|
1570
|
+
}
|
|
1571
|
+
if (input.comm_date !== undefined) {
|
|
1572
|
+
setClauses.push("comm_date = ?");
|
|
1573
|
+
params.push(input.comm_date);
|
|
1574
|
+
}
|
|
1575
|
+
if (input.type !== undefined) {
|
|
1576
|
+
setClauses.push("type = ?");
|
|
1577
|
+
params.push(input.type);
|
|
1578
|
+
}
|
|
1579
|
+
if (input.direction !== undefined) {
|
|
1580
|
+
setClauses.push("direction = ?");
|
|
1581
|
+
params.push(input.direction);
|
|
1582
|
+
}
|
|
1583
|
+
if ("subject" in input) {
|
|
1584
|
+
setClauses.push("subject = ?");
|
|
1585
|
+
params.push(input.subject ?? null);
|
|
1586
|
+
}
|
|
1587
|
+
if ("body" in input) {
|
|
1588
|
+
setClauses.push("body = ?");
|
|
1589
|
+
params.push(input.body ?? null);
|
|
1590
|
+
}
|
|
1591
|
+
if (input.status !== undefined) {
|
|
1592
|
+
setClauses.push("status = ?");
|
|
1593
|
+
params.push(input.status);
|
|
1594
|
+
}
|
|
1595
|
+
if ("invoice_amount" in input) {
|
|
1596
|
+
setClauses.push("invoice_amount = ?");
|
|
1597
|
+
params.push(input.invoice_amount ?? null);
|
|
1598
|
+
}
|
|
1599
|
+
if ("invoice_currency" in input) {
|
|
1600
|
+
setClauses.push("invoice_currency = ?");
|
|
1601
|
+
params.push(input.invoice_currency ?? null);
|
|
1602
|
+
}
|
|
1603
|
+
if ("invoice_ref" in input) {
|
|
1604
|
+
setClauses.push("invoice_ref = ?");
|
|
1605
|
+
params.push(input.invoice_ref ?? null);
|
|
1606
|
+
}
|
|
1607
|
+
if ("follow_up_date" in input) {
|
|
1608
|
+
setClauses.push("follow_up_date = ?");
|
|
1609
|
+
params.push(input.follow_up_date ?? null);
|
|
1610
|
+
}
|
|
1611
|
+
if (input.follow_up_done !== undefined) {
|
|
1612
|
+
setClauses.push("follow_up_done = ?");
|
|
1613
|
+
params.push(input.follow_up_done ? 1 : 0);
|
|
1614
|
+
}
|
|
1615
|
+
if (setClauses.length > 0) {
|
|
1616
|
+
params.push(id);
|
|
1617
|
+
d.run(`UPDATE vendor_communications SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
1618
|
+
}
|
|
1619
|
+
return rowToVendorComm(d.query(`SELECT * FROM vendor_communications WHERE id = ?`).get(id));
|
|
1620
|
+
}
|
|
1621
|
+
function deleteVendorCommunication(id, db) {
|
|
1622
|
+
const d = db || getDatabase();
|
|
1623
|
+
d.run(`DELETE FROM vendor_communications WHERE id = ?`, [id]);
|
|
1624
|
+
}
|
|
1625
|
+
function listPendingFollowUps(db) {
|
|
1626
|
+
const d = db || getDatabase();
|
|
1627
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
1628
|
+
const rows = d.query(`SELECT * FROM vendor_communications
|
|
1629
|
+
WHERE follow_up_date <= ? AND follow_up_done = 0
|
|
1630
|
+
ORDER BY follow_up_date ASC`).all(today);
|
|
1631
|
+
return rows.map(rowToVendorComm);
|
|
1632
|
+
}
|
|
1633
|
+
function listMissingInvoices(db) {
|
|
1634
|
+
const d = db || getDatabase();
|
|
1635
|
+
const rows = d.query(`SELECT * FROM vendor_communications
|
|
1636
|
+
WHERE type = 'invoice_request' AND status IN ('awaiting_response','no_response')
|
|
1637
|
+
ORDER BY comm_date ASC`).all();
|
|
1638
|
+
return rows.map(rowToVendorComm);
|
|
1639
|
+
}
|
|
1640
|
+
function markFollowUpDone(id, db) {
|
|
1641
|
+
const d = db || getDatabase();
|
|
1642
|
+
d.run(`UPDATE vendor_communications SET follow_up_done = 1 WHERE id = ?`, [id]);
|
|
1643
|
+
return rowToVendorComm(d.query(`SELECT * FROM vendor_communications WHERE id = ?`).get(id));
|
|
1644
|
+
}
|
|
1645
|
+
// src/db/contact-tasks.ts
|
|
1646
|
+
function rowToContactTask(row) {
|
|
1647
|
+
return {
|
|
1648
|
+
id: row.id,
|
|
1649
|
+
title: row.title,
|
|
1650
|
+
description: row.description,
|
|
1651
|
+
contact_id: row.contact_id,
|
|
1652
|
+
assigned_by: row.assigned_by,
|
|
1653
|
+
deadline: row.deadline,
|
|
1654
|
+
status: row.status,
|
|
1655
|
+
priority: row.priority,
|
|
1656
|
+
entity_id: row.entity_id,
|
|
1657
|
+
linked_todos_task_id: row.linked_todos_task_id,
|
|
1658
|
+
escalation_rules: JSON.parse(row.escalation_rules || "[]"),
|
|
1659
|
+
created_at: row.created_at,
|
|
1660
|
+
updated_at: row.updated_at
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
function createContactTask(input, db) {
|
|
1664
|
+
const d = db || getDatabase();
|
|
1665
|
+
const id = uuid();
|
|
1666
|
+
const timestamp = now();
|
|
1667
|
+
d.run(`INSERT INTO contact_tasks
|
|
1668
|
+
(id, title, description, contact_id, assigned_by, deadline, status, priority,
|
|
1669
|
+
entity_id, linked_todos_task_id, escalation_rules, created_at, updated_at)
|
|
1670
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1671
|
+
id,
|
|
1672
|
+
input.title,
|
|
1673
|
+
input.description ?? null,
|
|
1674
|
+
input.contact_id,
|
|
1675
|
+
input.assigned_by ?? null,
|
|
1676
|
+
input.deadline ?? null,
|
|
1677
|
+
input.status ?? "pending",
|
|
1678
|
+
input.priority ?? "medium",
|
|
1679
|
+
input.entity_id ?? null,
|
|
1680
|
+
input.linked_todos_task_id ?? null,
|
|
1681
|
+
JSON.stringify(input.escalation_rules ?? []),
|
|
1682
|
+
timestamp,
|
|
1683
|
+
timestamp
|
|
1684
|
+
]);
|
|
1685
|
+
return rowToContactTask(d.query(`SELECT * FROM contact_tasks WHERE id = ?`).get(id));
|
|
1686
|
+
}
|
|
1687
|
+
function getContactTask(id, db) {
|
|
1688
|
+
const d = db || getDatabase();
|
|
1689
|
+
const row = d.query(`SELECT * FROM contact_tasks WHERE id = ?`).get(id);
|
|
1690
|
+
return row ? rowToContactTask(row) : null;
|
|
1691
|
+
}
|
|
1692
|
+
function listContactTasks(opts = {}, db) {
|
|
1693
|
+
const d = db || getDatabase();
|
|
1694
|
+
const conditions = [];
|
|
1695
|
+
const params = [];
|
|
1696
|
+
if (opts.contact_id) {
|
|
1697
|
+
conditions.push("contact_id = ?");
|
|
1698
|
+
params.push(opts.contact_id);
|
|
1699
|
+
}
|
|
1700
|
+
if (opts.entity_id) {
|
|
1701
|
+
conditions.push("entity_id = ?");
|
|
1702
|
+
params.push(opts.entity_id);
|
|
1703
|
+
}
|
|
1704
|
+
if (opts.status) {
|
|
1705
|
+
conditions.push("status = ?");
|
|
1706
|
+
params.push(opts.status);
|
|
1707
|
+
}
|
|
1708
|
+
if (opts.priority) {
|
|
1709
|
+
conditions.push("priority = ?");
|
|
1710
|
+
params.push(opts.priority);
|
|
1711
|
+
}
|
|
1712
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1713
|
+
const rows = d.query(`SELECT * FROM contact_tasks ${where} ORDER BY deadline ASC, priority DESC, created_at ASC`).all(...params);
|
|
1714
|
+
return rows.map(rowToContactTask);
|
|
1715
|
+
}
|
|
1716
|
+
function updateContactTask(id, input, db) {
|
|
1717
|
+
const d = db || getDatabase();
|
|
1718
|
+
const setClauses = ["updated_at = ?"];
|
|
1719
|
+
const params = [now()];
|
|
1720
|
+
if (input.title !== undefined) {
|
|
1721
|
+
setClauses.push("title = ?");
|
|
1722
|
+
params.push(input.title);
|
|
1723
|
+
}
|
|
1724
|
+
if ("description" in input) {
|
|
1725
|
+
setClauses.push("description = ?");
|
|
1726
|
+
params.push(input.description ?? null);
|
|
1727
|
+
}
|
|
1728
|
+
if ("assigned_by" in input) {
|
|
1729
|
+
setClauses.push("assigned_by = ?");
|
|
1730
|
+
params.push(input.assigned_by ?? null);
|
|
1731
|
+
}
|
|
1732
|
+
if ("deadline" in input) {
|
|
1733
|
+
setClauses.push("deadline = ?");
|
|
1734
|
+
params.push(input.deadline ?? null);
|
|
1735
|
+
}
|
|
1736
|
+
if (input.status !== undefined) {
|
|
1737
|
+
setClauses.push("status = ?");
|
|
1738
|
+
params.push(input.status);
|
|
1739
|
+
}
|
|
1740
|
+
if (input.priority !== undefined) {
|
|
1741
|
+
setClauses.push("priority = ?");
|
|
1742
|
+
params.push(input.priority);
|
|
1743
|
+
}
|
|
1744
|
+
if ("entity_id" in input) {
|
|
1745
|
+
setClauses.push("entity_id = ?");
|
|
1746
|
+
params.push(input.entity_id ?? null);
|
|
1747
|
+
}
|
|
1748
|
+
if ("linked_todos_task_id" in input) {
|
|
1749
|
+
setClauses.push("linked_todos_task_id = ?");
|
|
1750
|
+
params.push(input.linked_todos_task_id ?? null);
|
|
1751
|
+
}
|
|
1752
|
+
if (input.escalation_rules !== undefined) {
|
|
1753
|
+
setClauses.push("escalation_rules = ?");
|
|
1754
|
+
params.push(JSON.stringify(input.escalation_rules));
|
|
1755
|
+
}
|
|
1756
|
+
params.push(id);
|
|
1757
|
+
d.run(`UPDATE contact_tasks SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
1758
|
+
return rowToContactTask(d.query(`SELECT * FROM contact_tasks WHERE id = ?`).get(id));
|
|
1759
|
+
}
|
|
1760
|
+
function deleteContactTask(id, db) {
|
|
1761
|
+
const d = db || getDatabase();
|
|
1762
|
+
d.run(`DELETE FROM contact_tasks WHERE id = ?`, [id]);
|
|
1763
|
+
}
|
|
1764
|
+
function listOverdueTasks(db) {
|
|
1765
|
+
const d = db || getDatabase();
|
|
1766
|
+
const now_iso = new Date().toISOString();
|
|
1767
|
+
const rows = d.query(`SELECT * FROM contact_tasks
|
|
1768
|
+
WHERE deadline < ? AND status NOT IN ('completed','cancelled')
|
|
1769
|
+
ORDER BY deadline ASC`).all(now_iso);
|
|
1770
|
+
return rows.map(rowToContactTask);
|
|
1771
|
+
}
|
|
1772
|
+
function checkEscalations(db) {
|
|
1773
|
+
const overdue = listOverdueTasks(db);
|
|
1774
|
+
const now_ms = Date.now();
|
|
1775
|
+
const results = [];
|
|
1776
|
+
for (const task of overdue) {
|
|
1777
|
+
if (!task.deadline || task.escalation_rules.length === 0)
|
|
1778
|
+
continue;
|
|
1779
|
+
const deadlineMs = new Date(task.deadline).getTime();
|
|
1780
|
+
const daysPastDeadline = (now_ms - deadlineMs) / (1000 * 60 * 60 * 24);
|
|
1781
|
+
const triggered = task.escalation_rules.filter((rule) => daysPastDeadline >= rule.after_days);
|
|
1782
|
+
if (triggered.length > 0) {
|
|
1783
|
+
results.push({ task, rules_triggered: triggered });
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
return results;
|
|
1787
|
+
}
|
|
1788
|
+
// src/db/applications.ts
|
|
1789
|
+
function rowToApplication(row) {
|
|
1790
|
+
return {
|
|
1791
|
+
id: row.id,
|
|
1792
|
+
program_name: row.program_name,
|
|
1793
|
+
provider_company_id: row.provider_company_id,
|
|
1794
|
+
type: row.type,
|
|
1795
|
+
value_usd: row.value_usd,
|
|
1796
|
+
applicant_contact_id: row.applicant_contact_id,
|
|
1797
|
+
primary_contact_id: row.primary_contact_id,
|
|
1798
|
+
status: row.status,
|
|
1799
|
+
submitted_date: row.submitted_date,
|
|
1800
|
+
decision_date: row.decision_date,
|
|
1801
|
+
follow_up_date: row.follow_up_date,
|
|
1802
|
+
notes: row.notes,
|
|
1803
|
+
method: row.method ?? null,
|
|
1804
|
+
form_url: row.form_url,
|
|
1805
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
1806
|
+
created_at: row.created_at,
|
|
1807
|
+
updated_at: row.updated_at
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
function createApplication(input, db) {
|
|
1811
|
+
const d = db || getDatabase();
|
|
1812
|
+
const id = uuid();
|
|
1813
|
+
const timestamp = now();
|
|
1814
|
+
d.run(`INSERT INTO applications
|
|
1815
|
+
(id, program_name, provider_company_id, type, value_usd, applicant_contact_id, primary_contact_id,
|
|
1816
|
+
status, submitted_date, decision_date, follow_up_date, notes, method, form_url, metadata, created_at, updated_at)
|
|
1817
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1818
|
+
id,
|
|
1819
|
+
input.program_name,
|
|
1820
|
+
input.provider_company_id ?? null,
|
|
1821
|
+
input.type ?? "other",
|
|
1822
|
+
input.value_usd ?? null,
|
|
1823
|
+
input.applicant_contact_id ?? null,
|
|
1824
|
+
input.primary_contact_id ?? null,
|
|
1825
|
+
input.status ?? "draft",
|
|
1826
|
+
input.submitted_date ?? null,
|
|
1827
|
+
input.decision_date ?? null,
|
|
1828
|
+
input.follow_up_date ?? null,
|
|
1829
|
+
input.notes ?? null,
|
|
1830
|
+
input.method ?? null,
|
|
1831
|
+
input.form_url ?? null,
|
|
1832
|
+
JSON.stringify(input.metadata ?? {}),
|
|
1833
|
+
timestamp,
|
|
1834
|
+
timestamp
|
|
1835
|
+
]);
|
|
1836
|
+
return rowToApplication(d.query(`SELECT * FROM applications WHERE id = ?`).get(id));
|
|
1837
|
+
}
|
|
1838
|
+
function getApplication(id, db) {
|
|
1839
|
+
const d = db || getDatabase();
|
|
1840
|
+
const row = d.query(`SELECT * FROM applications WHERE id = ?`).get(id);
|
|
1841
|
+
return row ? rowToApplication(row) : null;
|
|
1842
|
+
}
|
|
1843
|
+
function listApplications(opts = {}, db) {
|
|
1844
|
+
const d = db || getDatabase();
|
|
1845
|
+
const conditions = [];
|
|
1846
|
+
const params = [];
|
|
1847
|
+
if (opts.type) {
|
|
1848
|
+
conditions.push("type = ?");
|
|
1849
|
+
params.push(opts.type);
|
|
1850
|
+
}
|
|
1851
|
+
if (opts.status) {
|
|
1852
|
+
conditions.push("status = ?");
|
|
1853
|
+
params.push(opts.status);
|
|
1854
|
+
}
|
|
1855
|
+
if (opts.provider_company_id) {
|
|
1856
|
+
conditions.push("provider_company_id = ?");
|
|
1857
|
+
params.push(opts.provider_company_id);
|
|
1858
|
+
}
|
|
1859
|
+
if (opts.applicant_contact_id) {
|
|
1860
|
+
conditions.push("applicant_contact_id = ?");
|
|
1861
|
+
params.push(opts.applicant_contact_id);
|
|
1862
|
+
}
|
|
1863
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1864
|
+
const rows = d.query(`SELECT * FROM applications ${where} ORDER BY created_at DESC`).all(...params);
|
|
1865
|
+
return rows.map(rowToApplication);
|
|
1866
|
+
}
|
|
1867
|
+
function updateApplication(id, input, db) {
|
|
1868
|
+
const d = db || getDatabase();
|
|
1869
|
+
const setClauses = ["updated_at = ?"];
|
|
1870
|
+
const params = [now()];
|
|
1871
|
+
if (input.program_name !== undefined) {
|
|
1872
|
+
setClauses.push("program_name = ?");
|
|
1873
|
+
params.push(input.program_name);
|
|
1874
|
+
}
|
|
1875
|
+
if ("provider_company_id" in input) {
|
|
1876
|
+
setClauses.push("provider_company_id = ?");
|
|
1877
|
+
params.push(input.provider_company_id ?? null);
|
|
1878
|
+
}
|
|
1879
|
+
if (input.type !== undefined) {
|
|
1880
|
+
setClauses.push("type = ?");
|
|
1881
|
+
params.push(input.type);
|
|
1882
|
+
}
|
|
1883
|
+
if ("value_usd" in input) {
|
|
1884
|
+
setClauses.push("value_usd = ?");
|
|
1885
|
+
params.push(input.value_usd ?? null);
|
|
1886
|
+
}
|
|
1887
|
+
if ("applicant_contact_id" in input) {
|
|
1888
|
+
setClauses.push("applicant_contact_id = ?");
|
|
1889
|
+
params.push(input.applicant_contact_id ?? null);
|
|
1890
|
+
}
|
|
1891
|
+
if ("primary_contact_id" in input) {
|
|
1892
|
+
setClauses.push("primary_contact_id = ?");
|
|
1893
|
+
params.push(input.primary_contact_id ?? null);
|
|
1894
|
+
}
|
|
1895
|
+
if (input.status !== undefined) {
|
|
1896
|
+
setClauses.push("status = ?");
|
|
1897
|
+
params.push(input.status);
|
|
1898
|
+
}
|
|
1899
|
+
if ("submitted_date" in input) {
|
|
1900
|
+
setClauses.push("submitted_date = ?");
|
|
1901
|
+
params.push(input.submitted_date ?? null);
|
|
1902
|
+
}
|
|
1903
|
+
if ("decision_date" in input) {
|
|
1904
|
+
setClauses.push("decision_date = ?");
|
|
1905
|
+
params.push(input.decision_date ?? null);
|
|
1906
|
+
}
|
|
1907
|
+
if ("follow_up_date" in input) {
|
|
1908
|
+
setClauses.push("follow_up_date = ?");
|
|
1909
|
+
params.push(input.follow_up_date ?? null);
|
|
1910
|
+
}
|
|
1911
|
+
if ("notes" in input) {
|
|
1912
|
+
setClauses.push("notes = ?");
|
|
1913
|
+
params.push(input.notes ?? null);
|
|
1914
|
+
}
|
|
1915
|
+
if ("method" in input) {
|
|
1916
|
+
setClauses.push("method = ?");
|
|
1917
|
+
params.push(input.method ?? null);
|
|
1918
|
+
}
|
|
1919
|
+
if ("form_url" in input) {
|
|
1920
|
+
setClauses.push("form_url = ?");
|
|
1921
|
+
params.push(input.form_url ?? null);
|
|
1922
|
+
}
|
|
1923
|
+
if (input.metadata !== undefined) {
|
|
1924
|
+
setClauses.push("metadata = ?");
|
|
1925
|
+
params.push(JSON.stringify(input.metadata));
|
|
1926
|
+
}
|
|
1927
|
+
params.push(id);
|
|
1928
|
+
d.run(`UPDATE applications SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
1929
|
+
return rowToApplication(d.query(`SELECT * FROM applications WHERE id = ?`).get(id));
|
|
1930
|
+
}
|
|
1931
|
+
function deleteApplication(id, db) {
|
|
1932
|
+
const d = db || getDatabase();
|
|
1933
|
+
d.run(`DELETE FROM applications WHERE id = ?`, [id]);
|
|
1934
|
+
}
|
|
1935
|
+
function listFollowUpDue(db) {
|
|
1936
|
+
const d = db || getDatabase();
|
|
1937
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
1938
|
+
const rows = d.query(`SELECT * FROM applications
|
|
1939
|
+
WHERE follow_up_date <= ? AND status = 'follow_up_needed'
|
|
1940
|
+
ORDER BY follow_up_date ASC`).all(today);
|
|
1941
|
+
return rows.map(rowToApplication);
|
|
1942
|
+
}
|
|
1943
|
+
function listPendingApplications(db) {
|
|
1944
|
+
const d = db || getDatabase();
|
|
1945
|
+
const rows = d.query(`SELECT * FROM applications
|
|
1946
|
+
WHERE status IN ('draft','submitted','pending')
|
|
1947
|
+
ORDER BY created_at DESC`).all();
|
|
1948
|
+
return rows.map(rowToApplication);
|
|
1949
|
+
}
|
|
1950
|
+
// src/db/notes.ts
|
|
1951
|
+
function addNote(contactId, body, createdBy, db, companyId) {
|
|
1952
|
+
const d = db || getDatabase();
|
|
1953
|
+
const contact = d.query(`SELECT id FROM contacts WHERE id = ?`).get(contactId);
|
|
1954
|
+
if (!contact)
|
|
1955
|
+
throw new ContactNotFoundError(contactId);
|
|
1956
|
+
const id = uuid();
|
|
1957
|
+
d.run(`INSERT INTO contact_notes (id, contact_id, body, created_by, company_id) VALUES (?, ?, ?, ?, ?)`, [id, contactId, body, createdBy ?? null, companyId ?? null]);
|
|
1958
|
+
return d.query(`SELECT * FROM contact_notes WHERE id = ?`).get(id);
|
|
1959
|
+
}
|
|
1960
|
+
function listNotes(contactId, db) {
|
|
1961
|
+
const d = db || getDatabase();
|
|
1962
|
+
return d.query(`SELECT * FROM contact_notes WHERE contact_id = ? ORDER BY created_at ASC`).all(contactId);
|
|
1963
|
+
}
|
|
1964
|
+
function listNotesForContactAtCompany(contactId, companyId, db) {
|
|
1965
|
+
const d = db || getDatabase();
|
|
1966
|
+
return d.query(`SELECT * FROM contact_notes WHERE contact_id = ? AND company_id = ? ORDER BY created_at ASC`).all(contactId, companyId);
|
|
1967
|
+
}
|
|
1968
|
+
function deleteNote(noteId, db) {
|
|
1969
|
+
const d = db || getDatabase();
|
|
1970
|
+
d.run(`DELETE FROM contact_notes WHERE id = ?`, [noteId]);
|
|
1971
|
+
}
|
|
1972
|
+
function getNote(noteId, db) {
|
|
1973
|
+
const d = db || getDatabase();
|
|
1974
|
+
return d.query(`SELECT * FROM contact_notes WHERE id = ?`).get(noteId);
|
|
1975
|
+
}
|
|
1246
1976
|
// src/db/groups.ts
|
|
1247
1977
|
function createGroup(db, input) {
|
|
1248
1978
|
const id = uuid();
|
|
@@ -1312,65 +2042,465 @@ function listCompaniesInGroup(db, groupId) {
|
|
|
1312
2042
|
function listGroupsForCompany(db, companyId) {
|
|
1313
2043
|
return db.query(`SELECT g.* FROM groups g JOIN company_groups cog ON g.id = cog.group_id WHERE cog.company_id = ? ORDER BY g.name`).all(companyId);
|
|
1314
2044
|
}
|
|
2045
|
+
// src/lib/connector.ts
|
|
2046
|
+
import { join as join2 } from "path";
|
|
2047
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
2048
|
+
import { homedir } from "os";
|
|
2049
|
+
|
|
2050
|
+
class ConnectorNotInstalledError extends Error {
|
|
2051
|
+
connectorName;
|
|
2052
|
+
constructor(connectorName) {
|
|
2053
|
+
super(`connect-${connectorName} is not installed. ` + `Run: bun install -g @hasnaxyz/connect-${connectorName}`);
|
|
2054
|
+
this.connectorName = connectorName;
|
|
2055
|
+
this.name = "ConnectorNotInstalledError";
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
class ConnectorAuthError extends Error {
|
|
2060
|
+
connectorName;
|
|
2061
|
+
constructor(connectorName, detail) {
|
|
2062
|
+
super(`connect-${connectorName} is not authenticated. ` + `Run: connect-${connectorName} auth login` + (detail ? ` (${detail})` : ""));
|
|
2063
|
+
this.connectorName = connectorName;
|
|
2064
|
+
this.name = "ConnectorAuthError";
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
async function runConnector(name, args, opts = {}) {
|
|
2068
|
+
const binary = `connect-${name}`;
|
|
2069
|
+
const profile = opts.profile ?? "default";
|
|
2070
|
+
const fullArgs = [
|
|
2071
|
+
"--format",
|
|
2072
|
+
"json",
|
|
2073
|
+
"--profile",
|
|
2074
|
+
profile,
|
|
2075
|
+
...args
|
|
2076
|
+
];
|
|
2077
|
+
let proc;
|
|
2078
|
+
try {
|
|
2079
|
+
proc = Bun.spawn([binary, ...fullArgs], {
|
|
2080
|
+
stdout: "pipe",
|
|
2081
|
+
stderr: "pipe"
|
|
2082
|
+
});
|
|
2083
|
+
} catch (err) {
|
|
2084
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2085
|
+
if (msg.includes("not found") || msg.includes("ENOENT")) {
|
|
2086
|
+
throw new ConnectorNotInstalledError(name);
|
|
2087
|
+
}
|
|
2088
|
+
throw err;
|
|
2089
|
+
}
|
|
2090
|
+
const timeoutMs = opts.timeout ?? 30000;
|
|
2091
|
+
const timer = setTimeout(() => proc.kill(), timeoutMs);
|
|
2092
|
+
try {
|
|
2093
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
2094
|
+
proc.stdout ? new Response(proc.stdout).text() : Promise.resolve(""),
|
|
2095
|
+
proc.stderr ? new Response(proc.stderr).text() : Promise.resolve(""),
|
|
2096
|
+
proc.exited
|
|
2097
|
+
]);
|
|
2098
|
+
if (exitCode !== 0) {
|
|
2099
|
+
const errText = (stderr || stdout).trim();
|
|
2100
|
+
const lower = errText.toLowerCase();
|
|
2101
|
+
if (lower.includes("auth") || lower.includes("token") || lower.includes("401") || lower.includes("unauthorized") || lower.includes("not authenticated")) {
|
|
2102
|
+
throw new ConnectorAuthError(name, errText.slice(0, 200));
|
|
2103
|
+
}
|
|
2104
|
+
throw new Error(`connect-${name} exited ${exitCode}: ${errText.slice(0, 400)}`);
|
|
2105
|
+
}
|
|
2106
|
+
const text = stdout.trim();
|
|
2107
|
+
if (!text || text === "null")
|
|
2108
|
+
return null;
|
|
2109
|
+
return JSON.parse(text);
|
|
2110
|
+
} finally {
|
|
2111
|
+
clearTimeout(timer);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
function getConnectorTokenPath(name, profile = "default") {
|
|
2115
|
+
const bases = [
|
|
2116
|
+
join2(homedir(), ".connectors", `connect-${name}`, "profiles", profile, "tokens.json"),
|
|
2117
|
+
join2(homedir(), ".connect", `connect-${name}`, "profiles", profile, "tokens.json"),
|
|
2118
|
+
join2(homedir(), ".connect", `connect-${name}`, "tokens.json")
|
|
2119
|
+
];
|
|
2120
|
+
for (const p of bases) {
|
|
2121
|
+
if (existsSync2(p))
|
|
2122
|
+
return p;
|
|
2123
|
+
}
|
|
2124
|
+
return null;
|
|
2125
|
+
}
|
|
2126
|
+
function readConnectorTokens(name, profile = "default") {
|
|
2127
|
+
const path = getConnectorTokenPath(name, profile);
|
|
2128
|
+
if (!path)
|
|
2129
|
+
throw new ConnectorAuthError(name);
|
|
2130
|
+
try {
|
|
2131
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
2132
|
+
} catch {
|
|
2133
|
+
throw new ConnectorAuthError(name, "tokens file unreadable");
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
// src/lib/gmail-import.ts
|
|
2137
|
+
function parseAddressHeader(header) {
|
|
2138
|
+
const results = [];
|
|
2139
|
+
const parts = header.split(/,(?![^<>]*>)(?![^"]*")/);
|
|
2140
|
+
for (const part of parts) {
|
|
2141
|
+
const trimmed = part.trim();
|
|
2142
|
+
if (!trimmed)
|
|
2143
|
+
continue;
|
|
2144
|
+
const angleMatch = trimmed.match(/^(.*?)\s*<([^>]+)>\s*$/);
|
|
2145
|
+
if (angleMatch) {
|
|
2146
|
+
const name = angleMatch[1].trim().replace(/^"|"$/g, "");
|
|
2147
|
+
const email = angleMatch[2].trim().toLowerCase();
|
|
2148
|
+
if (email.includes("@"))
|
|
2149
|
+
results.push({ name, email });
|
|
2150
|
+
} else if (trimmed.includes("@")) {
|
|
2151
|
+
results.push({ name: "", email: trimmed.toLowerCase() });
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
return results;
|
|
2155
|
+
}
|
|
2156
|
+
function domainToCompany(email) {
|
|
2157
|
+
const domain = email.split("@")[1];
|
|
2158
|
+
if (!domain)
|
|
2159
|
+
return null;
|
|
2160
|
+
const genericDomains = new Set([
|
|
2161
|
+
"gmail.com",
|
|
2162
|
+
"googlemail.com",
|
|
2163
|
+
"yahoo.com",
|
|
2164
|
+
"yahoo.co.uk",
|
|
2165
|
+
"hotmail.com",
|
|
2166
|
+
"hotmail.co.uk",
|
|
2167
|
+
"outlook.com",
|
|
2168
|
+
"live.com",
|
|
2169
|
+
"icloud.com",
|
|
2170
|
+
"me.com",
|
|
2171
|
+
"mac.com",
|
|
2172
|
+
"protonmail.com",
|
|
2173
|
+
"pm.me",
|
|
2174
|
+
"fastmail.com",
|
|
2175
|
+
"hey.com",
|
|
2176
|
+
"aol.com",
|
|
2177
|
+
"msn.com"
|
|
2178
|
+
]);
|
|
2179
|
+
if (genericDomains.has(domain.toLowerCase()))
|
|
2180
|
+
return null;
|
|
2181
|
+
const parts = domain.split(".");
|
|
2182
|
+
const name = parts.length > 2 ? parts[parts.length - 2] : parts[0];
|
|
2183
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
2184
|
+
}
|
|
2185
|
+
function parseName(displayName) {
|
|
2186
|
+
const name = displayName.trim();
|
|
2187
|
+
if (!name)
|
|
2188
|
+
return {};
|
|
2189
|
+
const parts = name.split(/\s+/);
|
|
2190
|
+
if (parts.length === 1)
|
|
2191
|
+
return { first_name: parts[0] };
|
|
2192
|
+
const last = parts.pop();
|
|
2193
|
+
return { first_name: parts.join(" "), last_name: last };
|
|
2194
|
+
}
|
|
2195
|
+
async function extractContactsFromGmail(opts) {
|
|
2196
|
+
const maxMessages = Math.min(opts.max_messages ?? 200, 500);
|
|
2197
|
+
const profile = opts.gmail_profile ?? "default";
|
|
2198
|
+
let token;
|
|
2199
|
+
try {
|
|
2200
|
+
const tokens = readConnectorTokens("gmail", profile);
|
|
2201
|
+
token = tokens["accessToken"];
|
|
2202
|
+
if (!token)
|
|
2203
|
+
throw new ConnectorAuthError("gmail", "accessToken missing");
|
|
2204
|
+
} catch (err) {
|
|
2205
|
+
if (err instanceof ConnectorAuthError)
|
|
2206
|
+
throw err;
|
|
2207
|
+
throw new ConnectorAuthError("gmail", String(err));
|
|
2208
|
+
}
|
|
2209
|
+
const listUrl = new URL("https://gmail.googleapis.com/gmail/v1/users/me/messages");
|
|
2210
|
+
listUrl.searchParams.set("q", opts.query);
|
|
2211
|
+
listUrl.searchParams.set("maxResults", String(maxMessages));
|
|
2212
|
+
const listResp = await fetch(listUrl.toString(), {
|
|
2213
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
2214
|
+
});
|
|
2215
|
+
if (!listResp.ok) {
|
|
2216
|
+
if (listResp.status === 401) {
|
|
2217
|
+
throw new ConnectorAuthError("gmail", "token expired \u2014 run connect-gmail auth login");
|
|
2218
|
+
}
|
|
2219
|
+
throw new Error(`Gmail API error ${listResp.status}: ${await listResp.text()}`);
|
|
2220
|
+
}
|
|
2221
|
+
const listData = await listResp.json();
|
|
2222
|
+
const messageRefs = listData.messages ?? [];
|
|
2223
|
+
if (messageRefs.length === 0)
|
|
2224
|
+
return [];
|
|
2225
|
+
const seen = new Map;
|
|
2226
|
+
const batchSize = 20;
|
|
2227
|
+
for (let i = 0;i < messageRefs.length; i += batchSize) {
|
|
2228
|
+
const batch = messageRefs.slice(i, i + batchSize);
|
|
2229
|
+
const fetches = batch.map((ref) => {
|
|
2230
|
+
const url = new URL(`https://gmail.googleapis.com/gmail/v1/users/me/messages/${ref.id}`);
|
|
2231
|
+
url.searchParams.set("format", "metadata");
|
|
2232
|
+
url.searchParams.append("metadataHeaders", "From");
|
|
2233
|
+
url.searchParams.append("metadataHeaders", "To");
|
|
2234
|
+
url.searchParams.append("metadataHeaders", "Cc");
|
|
2235
|
+
return fetch(url.toString(), {
|
|
2236
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
2237
|
+
}).then((r) => r.ok ? r.json() : null);
|
|
2238
|
+
});
|
|
2239
|
+
const results = await Promise.all(fetches);
|
|
2240
|
+
for (const msg of results) {
|
|
2241
|
+
if (!msg?.payload?.headers)
|
|
2242
|
+
continue;
|
|
2243
|
+
const getHeader = (name) => msg.payload.headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? "";
|
|
2244
|
+
const addresses = [
|
|
2245
|
+
...parseAddressHeader(getHeader("From")),
|
|
2246
|
+
...parseAddressHeader(getHeader("To")),
|
|
2247
|
+
...parseAddressHeader(getHeader("Cc"))
|
|
2248
|
+
];
|
|
2249
|
+
for (const { name, email } of addresses) {
|
|
2250
|
+
if (seen.has(email))
|
|
2251
|
+
continue;
|
|
2252
|
+
const company_hint = domainToCompany(email);
|
|
2253
|
+
const nameParts = parseName(name);
|
|
2254
|
+
const contact_input = {
|
|
2255
|
+
...nameParts,
|
|
2256
|
+
display_name: name || email.split("@")[0],
|
|
2257
|
+
emails: [{ address: email, type: "work", is_primary: true }],
|
|
2258
|
+
...opts.tag_ids?.length ? { tag_ids: opts.tag_ids } : {},
|
|
2259
|
+
source: "email"
|
|
2260
|
+
};
|
|
2261
|
+
seen.set(email, { email, name, company_hint, contact_input });
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
return Array.from(seen.values());
|
|
2266
|
+
}
|
|
2267
|
+
// src/lib/google-contacts.ts
|
|
2268
|
+
function googlePersonToContactInput(person) {
|
|
2269
|
+
const name = person.names?.[0];
|
|
2270
|
+
const primaryEmail = person.emailAddresses?.[0]?.value;
|
|
2271
|
+
const nameParts = name?.givenName || name?.familyName ? { first_name: name.givenName, last_name: name.familyName } : primaryEmail ? parseName(name?.displayName ?? primaryEmail.split("@")[0] ?? "") : {};
|
|
2272
|
+
const emails = (person.emailAddresses ?? []).filter((e) => e.value).map((e, i) => ({
|
|
2273
|
+
address: e.value.toLowerCase(),
|
|
2274
|
+
type: normalizeEmailType(e.type),
|
|
2275
|
+
is_primary: i === 0
|
|
2276
|
+
}));
|
|
2277
|
+
const phones = (person.phoneNumbers ?? []).filter((p) => p.value).map((p, i) => ({
|
|
2278
|
+
number: p.value,
|
|
2279
|
+
type: normalizePhoneType(p.type),
|
|
2280
|
+
is_primary: i === 0
|
|
2281
|
+
}));
|
|
2282
|
+
const org = person.organizations?.[0];
|
|
2283
|
+
const website = person.urls?.[0]?.value;
|
|
2284
|
+
const birthday = (() => {
|
|
2285
|
+
const b = person.birthdays?.[0]?.date;
|
|
2286
|
+
if (!b?.year || !b?.month || !b?.day)
|
|
2287
|
+
return;
|
|
2288
|
+
const mm = String(b.month).padStart(2, "0");
|
|
2289
|
+
const dd = String(b.day).padStart(2, "0");
|
|
2290
|
+
return `${b.year}-${mm}-${dd}`;
|
|
2291
|
+
})();
|
|
2292
|
+
const notes = person.biographies?.map((b) => b.value).filter(Boolean).join(`
|
|
2293
|
+
|
|
2294
|
+
`) || undefined;
|
|
2295
|
+
return {
|
|
2296
|
+
...nameParts,
|
|
2297
|
+
display_name: name?.displayName ?? (nameParts.first_name ? `${nameParts.first_name} ${nameParts.last_name ?? ""}`.trim() : primaryEmail?.split("@")[0] ?? "Unknown"),
|
|
2298
|
+
job_title: org?.title,
|
|
2299
|
+
notes,
|
|
2300
|
+
birthday,
|
|
2301
|
+
website,
|
|
2302
|
+
source: "import",
|
|
2303
|
+
emails,
|
|
2304
|
+
phones,
|
|
2305
|
+
custom_fields: { google_resource_name: person.resourceName }
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
function normalizeEmailType(type) {
|
|
2309
|
+
if (!type)
|
|
2310
|
+
return "work";
|
|
2311
|
+
const t = type.toLowerCase();
|
|
2312
|
+
if (t === "work" || t === "home" || t === "personal")
|
|
2313
|
+
return t === "home" ? "personal" : "work";
|
|
2314
|
+
return "other";
|
|
2315
|
+
}
|
|
2316
|
+
function normalizePhoneType(type) {
|
|
2317
|
+
if (!type)
|
|
2318
|
+
return "mobile";
|
|
2319
|
+
const t = type.toLowerCase();
|
|
2320
|
+
if (t === "mobile" || t === "cell")
|
|
2321
|
+
return "mobile";
|
|
2322
|
+
if (t === "work")
|
|
2323
|
+
return "work";
|
|
2324
|
+
if (t === "home")
|
|
2325
|
+
return "home";
|
|
2326
|
+
if (t === "fax")
|
|
2327
|
+
return "fax";
|
|
2328
|
+
return "other";
|
|
2329
|
+
}
|
|
2330
|
+
function contactToGoogleArgs(contact) {
|
|
2331
|
+
const args = [];
|
|
2332
|
+
const displayName = contact.display_name;
|
|
2333
|
+
if (displayName)
|
|
2334
|
+
args.push("--name", displayName);
|
|
2335
|
+
if (contact.first_name)
|
|
2336
|
+
args.push("--given-name", contact.first_name);
|
|
2337
|
+
if (contact.last_name)
|
|
2338
|
+
args.push("--family-name", contact.last_name);
|
|
2339
|
+
const primaryEmail = contact.emails.find((e) => e.is_primary) ?? contact.emails[0];
|
|
2340
|
+
if (primaryEmail)
|
|
2341
|
+
args.push("--email", primaryEmail.address);
|
|
2342
|
+
const primaryPhone = contact.phones.find((p) => p.is_primary) ?? contact.phones[0];
|
|
2343
|
+
if (primaryPhone)
|
|
2344
|
+
args.push("--phone", primaryPhone.number);
|
|
2345
|
+
if (contact.job_title)
|
|
2346
|
+
args.push("--title", contact.job_title);
|
|
2347
|
+
if (contact.company)
|
|
2348
|
+
args.push("--company", contact.company.name);
|
|
2349
|
+
if (contact.notes)
|
|
2350
|
+
args.push("--notes", contact.notes);
|
|
2351
|
+
if (contact.website)
|
|
2352
|
+
args.push("--url", contact.website);
|
|
2353
|
+
return args;
|
|
2354
|
+
}
|
|
2355
|
+
async function listGoogleContacts(opts = {}) {
|
|
2356
|
+
const args = ["contacts", "list"];
|
|
2357
|
+
if (opts.page_size)
|
|
2358
|
+
args.push("--page-size", String(opts.page_size));
|
|
2359
|
+
const raw = await runConnector("googlecontacts", args, opts);
|
|
2360
|
+
return normalizeGoogleContactsResponse(raw);
|
|
2361
|
+
}
|
|
2362
|
+
async function searchGoogleContacts(query, opts = {}) {
|
|
2363
|
+
const raw = await runConnector("googlecontacts", ["contacts", "search", query], opts);
|
|
2364
|
+
return normalizeGoogleContactsResponse(raw);
|
|
2365
|
+
}
|
|
2366
|
+
function normalizeGoogleContactsResponse(raw) {
|
|
2367
|
+
if (!raw)
|
|
2368
|
+
return [];
|
|
2369
|
+
if (Array.isArray(raw))
|
|
2370
|
+
return raw;
|
|
2371
|
+
const obj = raw;
|
|
2372
|
+
if (Array.isArray(obj["connections"]))
|
|
2373
|
+
return obj["connections"];
|
|
2374
|
+
if (Array.isArray(obj["contacts"]))
|
|
2375
|
+
return obj["contacts"];
|
|
2376
|
+
return [];
|
|
2377
|
+
}
|
|
2378
|
+
async function pushContactToGoogle(contact, opts = {}) {
|
|
2379
|
+
const googleResourceName = contact.custom_fields?.["google_resource_name"];
|
|
2380
|
+
if (googleResourceName && opts.update_existing) {
|
|
2381
|
+
const args2 = ["contacts", "update", googleResourceName, ...contactToGoogleArgs(contact)];
|
|
2382
|
+
const result2 = await runConnector("googlecontacts", args2, opts);
|
|
2383
|
+
return { resourceName: result2?.resourceName ?? googleResourceName, action: "updated" };
|
|
2384
|
+
}
|
|
2385
|
+
const args = ["contacts", "create", ...contactToGoogleArgs(contact)];
|
|
2386
|
+
const result = await runConnector("googlecontacts", args, opts);
|
|
2387
|
+
return { resourceName: result?.resourceName ?? "", action: "created" };
|
|
2388
|
+
}
|
|
2389
|
+
async function pullGoogleContactsAsInputs(opts = {}) {
|
|
2390
|
+
const people = opts.query ? await searchGoogleContacts(opts.query, opts) : await listGoogleContacts(opts);
|
|
2391
|
+
return people.filter((p) => p.emailAddresses?.some((e) => e.value)).map(googlePersonToContactInput);
|
|
2392
|
+
}
|
|
1315
2393
|
export {
|
|
2394
|
+
updateVendorCommunication,
|
|
1316
2395
|
updateTag,
|
|
2396
|
+
updateOrgMember,
|
|
1317
2397
|
updateGroup,
|
|
2398
|
+
updateContactTask,
|
|
1318
2399
|
updateContact,
|
|
1319
2400
|
updateCompany,
|
|
2401
|
+
updateApplication,
|
|
1320
2402
|
unarchiveContact,
|
|
1321
2403
|
unarchiveCompany,
|
|
2404
|
+
searchGoogleContacts,
|
|
1322
2405
|
searchContacts,
|
|
1323
2406
|
searchCompanies,
|
|
2407
|
+
runConnector,
|
|
1324
2408
|
resetDatabase,
|
|
1325
2409
|
removeTagFromContact,
|
|
1326
2410
|
removeTagFromCompany,
|
|
2411
|
+
removeOrgMember,
|
|
1327
2412
|
removeContactFromGroup,
|
|
1328
2413
|
removeCompanyFromGroup,
|
|
2414
|
+
readConnectorTokens,
|
|
2415
|
+
pushContactToGoogle,
|
|
2416
|
+
pullGoogleContactsAsInputs,
|
|
2417
|
+
parseName,
|
|
2418
|
+
parseAddressHeader,
|
|
1329
2419
|
mergeContacts,
|
|
2420
|
+
markFollowUpDone,
|
|
2421
|
+
logVendorCommunication,
|
|
1330
2422
|
logActivity,
|
|
2423
|
+
listVendorCommunications,
|
|
1331
2424
|
listTags,
|
|
1332
2425
|
listRelationships,
|
|
1333
2426
|
listRecentContacts,
|
|
2427
|
+
listPendingFollowUps,
|
|
2428
|
+
listPendingApplications,
|
|
2429
|
+
listOwnedEntities,
|
|
2430
|
+
listOverdueTasks,
|
|
2431
|
+
listOrgMembersForContact,
|
|
2432
|
+
listOrgMembers,
|
|
2433
|
+
listNotesForContactAtCompany,
|
|
2434
|
+
listNotes,
|
|
2435
|
+
listMissingInvoices,
|
|
1334
2436
|
listGroupsForContact,
|
|
1335
2437
|
listGroupsForCompany,
|
|
1336
2438
|
listGroups,
|
|
2439
|
+
listGoogleContacts,
|
|
2440
|
+
listFollowUpDue,
|
|
1337
2441
|
listContactsInGroup,
|
|
1338
2442
|
listContactsByTag,
|
|
1339
2443
|
listContacts,
|
|
2444
|
+
listContactTasks,
|
|
2445
|
+
listCompanyRelationships,
|
|
1340
2446
|
listCompanyEmployees,
|
|
1341
2447
|
listCompaniesInGroup,
|
|
1342
2448
|
listCompanies,
|
|
2449
|
+
listApplications,
|
|
1343
2450
|
listActivity,
|
|
2451
|
+
googlePersonToContactInput,
|
|
1344
2452
|
getTagByName,
|
|
1345
2453
|
getTag,
|
|
1346
2454
|
getRelationship,
|
|
2455
|
+
getOrgMember,
|
|
2456
|
+
getNote,
|
|
1347
2457
|
getGroup,
|
|
2458
|
+
getEntityTeam,
|
|
1348
2459
|
getDatabase,
|
|
2460
|
+
getContactTask,
|
|
1349
2461
|
getContactByEmail,
|
|
1350
2462
|
getContact,
|
|
2463
|
+
getConnectorTokenPath,
|
|
1351
2464
|
getCompany,
|
|
2465
|
+
getApplication,
|
|
1352
2466
|
getActivity,
|
|
2467
|
+
extractContactsFromGmail,
|
|
2468
|
+
domainToCompany,
|
|
2469
|
+
deleteVendorCommunication,
|
|
1353
2470
|
deleteTag,
|
|
1354
2471
|
deleteRelationship,
|
|
2472
|
+
deleteNote,
|
|
1355
2473
|
deleteGroup,
|
|
2474
|
+
deleteContactTask,
|
|
1356
2475
|
deleteContact,
|
|
2476
|
+
deleteCompanyRelationship,
|
|
1357
2477
|
deleteCompany,
|
|
2478
|
+
deleteApplication,
|
|
1358
2479
|
createTag,
|
|
1359
2480
|
createRelationship,
|
|
1360
2481
|
createGroup,
|
|
2482
|
+
createContactTask,
|
|
1361
2483
|
createContact,
|
|
2484
|
+
createCompanyRelationship,
|
|
1362
2485
|
createCompany,
|
|
2486
|
+
createApplication,
|
|
2487
|
+
contactToGoogleArgs,
|
|
2488
|
+
checkEscalations,
|
|
1363
2489
|
autoLinkContactToCompany,
|
|
1364
2490
|
archiveContact,
|
|
1365
2491
|
archiveCompany,
|
|
1366
2492
|
addTagToContact,
|
|
1367
2493
|
addTagToCompany,
|
|
1368
2494
|
addPhoneToContact,
|
|
2495
|
+
addOrgMember,
|
|
2496
|
+
addNote,
|
|
1369
2497
|
addEmailToContact,
|
|
1370
2498
|
addContactToGroup,
|
|
1371
2499
|
addCompanyToGroup,
|
|
1372
2500
|
TagNotFoundError,
|
|
1373
2501
|
DuplicateTagNameError,
|
|
1374
2502
|
ContactNotFoundError,
|
|
2503
|
+
ConnectorNotInstalledError,
|
|
2504
|
+
ConnectorAuthError,
|
|
1375
2505
|
CompanyNotFoundError
|
|
1376
2506
|
};
|