176 lines
6.6 KiB
JavaScript
176 lines
6.6 KiB
JavaScript
const { all, audit, json, now, one, parseJson, run } = require('../db');
|
|
|
|
const TYPES = ['employee', 'vendor', 'service_provider', 'contractor', 'work_location', 'other'];
|
|
const CREDIT_TERMS = ['na', 'net_30', 'net_60', 'net_90', 'net_180'];
|
|
|
|
const COLUMNS = [
|
|
'type', 'first_name', 'last_name', 'organization', 'email', 'phone',
|
|
'address_line1', 'address_line2', 'city', 'state_region', 'postal_code', 'country',
|
|
'notes', 'status', 'employee_id', 'tax_id', 'department', 'work_location',
|
|
'manager_first_name', 'manager_last_name', 'manager_email', 'start_date',
|
|
'rating', 'credit_term', 'source', 'workday_worker_id'
|
|
];
|
|
|
|
function contactNotes(contactId) {
|
|
return all(
|
|
`SELECT n.id, n.body, n.created_at, u.name AS author
|
|
FROM contact_notes n LEFT JOIN users u ON u.id = n.created_by
|
|
WHERE n.contact_id = ? ORDER BY n.id DESC`,
|
|
[contactId]
|
|
);
|
|
}
|
|
|
|
function displayName(row) {
|
|
const person = [row.first_name, row.last_name].filter(Boolean).join(' ').trim();
|
|
return row.organization || person || row.email || `Contact ${row.id}`;
|
|
}
|
|
|
|
function fromRow(row, withNotes = false) {
|
|
if (!row) return null;
|
|
const contact = { ...row, display_name: displayName(row) };
|
|
if (withNotes) contact.contact_notes = contactNotes(row.id);
|
|
return contact;
|
|
}
|
|
|
|
function normalize(body) {
|
|
const input = {};
|
|
for (const column of COLUMNS) {
|
|
if (Object.prototype.hasOwnProperty.call(body, column)) input[column] = body[column];
|
|
}
|
|
input.type = TYPES.includes(input.type) ? input.type : 'other';
|
|
input.status = input.status || 'active';
|
|
input.source = input.source || 'manual';
|
|
if (input.credit_term && !CREDIT_TERMS.includes(input.credit_term)) input.credit_term = 'na';
|
|
if (input.rating != null && input.rating !== '') input.rating = Math.max(0, Math.min(5, Number(input.rating)));
|
|
else input.rating = null;
|
|
if (input.workday_worker_id === '') input.workday_worker_id = null;
|
|
return input;
|
|
}
|
|
|
|
function listContacts(query = {}) {
|
|
return all(
|
|
`SELECT * FROM contacts
|
|
WHERE (? IS NULL OR type = ?)
|
|
AND (? IS NULL OR status = ?)
|
|
AND (? IS NULL OR (
|
|
first_name LIKE '%' || ? || '%' OR last_name LIKE '%' || ? || '%' OR
|
|
organization LIKE '%' || ? || '%' OR email LIKE '%' || ? || '%' OR
|
|
employee_id LIKE '%' || ? || '%'
|
|
))
|
|
ORDER BY type, organization, last_name, first_name`,
|
|
[
|
|
query.type || null, query.type || null,
|
|
query.status || null, query.status || null,
|
|
query.search || null, query.search || null, query.search || null,
|
|
query.search || null, query.search || null, query.search || null
|
|
]
|
|
).map((row) => fromRow(row));
|
|
}
|
|
|
|
function getContact(id) {
|
|
return fromRow(one('SELECT * FROM contacts WHERE id = ?', [id]), true);
|
|
}
|
|
|
|
function createContact(body, userId) {
|
|
const input = normalize(body);
|
|
const ts = now();
|
|
const cols = COLUMNS.filter((c) => input[c] !== undefined);
|
|
const result = run(
|
|
`INSERT INTO contacts (${cols.join(', ')}, created_by, created_at, updated_at)
|
|
VALUES (${cols.map(() => '?').join(', ')}, ?, ?, ?)`,
|
|
[...cols.map((c) => input[c]), userId, ts, ts]
|
|
);
|
|
audit(userId, 'create', 'contact', result.lastInsertRowid, null, { type: input.type, name: displayName(input) });
|
|
return getContact(result.lastInsertRowid);
|
|
}
|
|
|
|
function updateContact(id, body, userId) {
|
|
const before = one('SELECT * FROM contacts WHERE id = ?', [id]);
|
|
if (!before) return null;
|
|
const input = normalize({ ...before, ...body });
|
|
run(
|
|
`UPDATE contacts SET ${COLUMNS.map((c) => `${c} = ?`).join(', ')}, updated_at = ? WHERE id = ?`,
|
|
[...COLUMNS.map((c) => input[c] ?? null), now(), id]
|
|
);
|
|
audit(userId, 'update', 'contact', id, before, body);
|
|
return getContact(id);
|
|
}
|
|
|
|
function deleteContact(id, userId) {
|
|
const before = one('SELECT * FROM contacts WHERE id = ?', [id]);
|
|
if (!before) return false;
|
|
run('DELETE FROM contacts WHERE id = ?', [id]);
|
|
audit(userId, 'delete', 'contact', id, before, null);
|
|
return true;
|
|
}
|
|
|
|
function addNote(contactId, bodyText, userId) {
|
|
if (!one('SELECT id FROM contacts WHERE id = ?', [contactId])) return null;
|
|
const result = run('INSERT INTO contact_notes (contact_id, body, created_by, created_at) VALUES (?, ?, ?, ?)', [contactId, bodyText, userId, now()]);
|
|
audit(userId, 'create', 'contact_note', result.lastInsertRowid, null, { contact_id: contactId });
|
|
return one('SELECT n.id, n.body, n.created_at, u.name AS author FROM contact_notes n LEFT JOIN users u ON u.id = n.created_by WHERE n.id = ?', [result.lastInsertRowid]);
|
|
}
|
|
|
|
function deleteNote(contactId, noteId, userId) {
|
|
const result = run('DELETE FROM contact_notes WHERE id = ? AND contact_id = ?', [noteId, contactId]);
|
|
if (!result.changes) return false;
|
|
audit(userId, 'delete', 'contact_note', noteId, null, { contact_id: contactId });
|
|
return true;
|
|
}
|
|
|
|
// Upsert an employee contact from a normalized Workday worker record.
|
|
function upsertWorkdayContact(worker) {
|
|
const ts = now();
|
|
const existing = worker.workday_worker_id
|
|
? one('SELECT id FROM contacts WHERE workday_worker_id = ?', [worker.workday_worker_id])
|
|
: (worker.email ? one('SELECT id FROM contacts WHERE type = ? AND lower(email) = lower(?)', ['employee', worker.email]) : null);
|
|
|
|
const fields = {
|
|
type: 'employee',
|
|
first_name: worker.first_name || null,
|
|
last_name: worker.last_name || null,
|
|
organization: null,
|
|
email: worker.email || null,
|
|
department: worker.department || null,
|
|
work_location: worker.location || null,
|
|
employee_id: worker.employee_number || null,
|
|
manager_first_name: worker.manager_first_name || null,
|
|
manager_last_name: worker.manager_last_name || null,
|
|
manager_email: worker.manager_email || null,
|
|
start_date: worker.start_date || null,
|
|
status: worker.status || 'active',
|
|
source: 'workday',
|
|
workday_worker_id: worker.workday_worker_id || null,
|
|
source_payload: json(worker.source_payload || worker)
|
|
};
|
|
|
|
if (existing) {
|
|
const cols = Object.keys(fields);
|
|
run(
|
|
`UPDATE contacts SET ${cols.map((c) => `${c} = COALESCE(?, ${c})`).join(', ')}, last_synced_at = ?, updated_at = ? WHERE id = ?`,
|
|
[...cols.map((c) => fields[c]), ts, ts, existing.id]
|
|
);
|
|
return existing.id;
|
|
}
|
|
const cols = Object.keys(fields);
|
|
const result = run(
|
|
`INSERT INTO contacts (${cols.join(', ')}, last_synced_at, created_at, updated_at) VALUES (${cols.map(() => '?').join(', ')}, ?, ?, ?)`,
|
|
[...cols.map((c) => fields[c]), ts, ts, ts]
|
|
);
|
|
return result.lastInsertRowid;
|
|
}
|
|
|
|
module.exports = {
|
|
CREDIT_TERMS,
|
|
TYPES,
|
|
addNote,
|
|
createContact,
|
|
deleteContact,
|
|
deleteNote,
|
|
getContact,
|
|
listContacts,
|
|
parsePayload: (value) => parseJson(value, null),
|
|
updateContact,
|
|
upsertWorkdayContact
|
|
};
|