FreeScout: use Customer and Website custom fields instead of customers table
This commit is contained in:
parent
749ee04ed7
commit
7c3f28ca84
1 changed files with 57 additions and 25 deletions
|
|
@ -19,11 +19,44 @@ async function getConn() {
|
|||
});
|
||||
}
|
||||
|
||||
// Load custom field dropdown options (id -> {optionId: label})
|
||||
async function loadCustomFieldOptions(conn) {
|
||||
const [fields] = await conn.query('SELECT id, name, options FROM custom_fields');
|
||||
const map = {};
|
||||
fields.forEach(f => {
|
||||
map[f.name.toLowerCase()] = { id: f.id, options: f.options ? JSON.parse(f.options) : {} };
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
function formatHours(seconds) {
|
||||
const h = seconds / 3600;
|
||||
return Math.round(h * 100) / 100;
|
||||
}
|
||||
|
||||
// Get custom field values for conversations, resolved to labels
|
||||
async function getConversationCustomFields(conn, conversationIds, cfMap) {
|
||||
if (!conversationIds.length) return {};
|
||||
const [rows] = await conn.query(
|
||||
'SELECT conversation_id, custom_field_id, value FROM conversation_custom_field WHERE conversation_id IN (?)',
|
||||
[conversationIds]
|
||||
);
|
||||
// Build: { convId: { fieldName: resolvedValue } }
|
||||
const result = {};
|
||||
const idToField = {};
|
||||
Object.entries(cfMap).forEach(([name, f]) => { idToField[f.id] = { name, options: f.options }; });
|
||||
|
||||
rows.forEach(r => {
|
||||
if (!result[r.conversation_id]) result[r.conversation_id] = {};
|
||||
const field = idToField[r.custom_field_id];
|
||||
if (field) {
|
||||
const resolved = field.options[r.value] || r.value || '-';
|
||||
result[r.conversation_id][field.name] = resolved;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- Commands ---
|
||||
|
||||
async function cmdMailboxes() {
|
||||
|
|
@ -54,9 +87,10 @@ async function cmdReport(args) {
|
|||
}
|
||||
|
||||
const conn = await getConn();
|
||||
const cfMap = await loadCustomFieldOptions(conn);
|
||||
|
||||
let query = `
|
||||
SELECT
|
||||
t.id as timelog_id,
|
||||
t.time_spent,
|
||||
t.created_at,
|
||||
c.id as conversation_id,
|
||||
|
|
@ -87,13 +121,17 @@ async function cmdReport(args) {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log('DATE\tCONV#\tSUBJECT\tCUSTOMER\tUSER\tHOURS\tMAILBOX');
|
||||
const convIds = [...new Set(rows.map(r => r.conversation_id))];
|
||||
const cfValues = await getConversationCustomFields(conn, convIds, cfMap);
|
||||
|
||||
console.log('DATE\tCONV#\tSUBJECT\tCUSTOMER\tWEBSITE\tUSER\tHOURS');
|
||||
let totalSeconds = 0;
|
||||
rows.forEach(r => {
|
||||
const date = new Date(r.created_at).toISOString().slice(0, 10);
|
||||
const hours = formatHours(r.time_spent);
|
||||
totalSeconds += r.time_spent;
|
||||
console.log(`${date}\t#${r.conversation_number}\t${r.subject || '(no subject)'}\t${r.customer_email || '-'}\t${r.first_name} ${r.last_name}\t${hours}\t${r.mailbox_name}`);
|
||||
const cf = cfValues[r.conversation_id] || {};
|
||||
console.log(`${date}\t#${r.conversation_number}\t${r.subject || '(no subject)'}\t${cf.customer || '-'}\t${cf.website || '-'}\t${r.first_name} ${r.last_name}\t${hours}`);
|
||||
});
|
||||
console.log(`\nTotal: ${formatHours(totalSeconds)} hours (${rows.length} entries)`);
|
||||
|
||||
|
|
@ -113,24 +151,20 @@ async function cmdExcel(args) {
|
|||
}
|
||||
|
||||
const conn = await getConn();
|
||||
const cfMap = await loadCustomFieldOptions(conn);
|
||||
|
||||
let query = `
|
||||
SELECT
|
||||
t.time_spent,
|
||||
t.created_at,
|
||||
c.id as conversation_id,
|
||||
c.number as conversation_number,
|
||||
c.subject,
|
||||
c.customer_email,
|
||||
m.name as mailbox_name,
|
||||
u.first_name,
|
||||
u.last_name,
|
||||
cu.first_name as customer_first,
|
||||
cu.last_name as customer_last,
|
||||
cu.company as customer_company
|
||||
c.customer_email
|
||||
FROM timelogs t
|
||||
JOIN conversations c ON t.conversation_id = c.id
|
||||
JOIN mailboxes m ON c.mailbox_id = m.id
|
||||
JOIN users u ON t.user_id = u.id
|
||||
LEFT JOIN customers cu ON c.customer_id = cu.id
|
||||
WHERE t.created_at >= ? AND t.created_at < DATE_ADD(?, INTERVAL 1 DAY)
|
||||
`;
|
||||
const params = [from, to];
|
||||
|
|
@ -141,47 +175,46 @@ async function cmdExcel(args) {
|
|||
query += ' ORDER BY t.created_at';
|
||||
|
||||
const [rows] = await conn.query(query, params);
|
||||
await conn.end();
|
||||
|
||||
if (rows.length === 0) {
|
||||
console.log('No timelogs found for this period.');
|
||||
await conn.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const convIds = [...new Set(rows.map(r => r.conversation_id))];
|
||||
const cfValues = await getConversationCustomFields(conn, convIds, cfMap);
|
||||
await conn.end();
|
||||
|
||||
// Group by conversation
|
||||
const byConv = {};
|
||||
rows.forEach(r => {
|
||||
const key = r.conversation_number;
|
||||
if (!byConv[key]) {
|
||||
const cf = cfValues[r.conversation_id] || {};
|
||||
byConv[key] = {
|
||||
number: r.conversation_number,
|
||||
subject: r.subject || '(no subject)',
|
||||
customer_email: r.customer_email || '-',
|
||||
customer_name: [r.customer_first, r.customer_last].filter(Boolean).join(' ') || '-',
|
||||
customer_company: r.customer_company || '-',
|
||||
mailbox: r.mailbox_name,
|
||||
customer: cf.customer || '-',
|
||||
website: cf.website || '-',
|
||||
totalSeconds: 0,
|
||||
entries: []
|
||||
};
|
||||
}
|
||||
byConv[key].totalSeconds += r.time_spent;
|
||||
byConv[key].entries.push(r);
|
||||
});
|
||||
|
||||
// Build Excel
|
||||
const wb = new ExcelJS.Workbook();
|
||||
const ws = wb.addWorksheet('Time Report');
|
||||
|
||||
// Header
|
||||
ws.columns = [
|
||||
{ header: 'Conv #', key: 'number', width: 10 },
|
||||
{ header: 'Subject', key: 'subject', width: 40 },
|
||||
{ header: 'Customer', key: 'customer', width: 25 },
|
||||
{ header: 'Company', key: 'company', width: 25 },
|
||||
{ header: 'Website', key: 'website', width: 25 },
|
||||
{ header: 'Hours', key: 'hours', width: 10 },
|
||||
];
|
||||
|
||||
// Style header
|
||||
ws.getRow(1).font = { bold: true };
|
||||
ws.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } };
|
||||
|
||||
|
|
@ -191,14 +224,13 @@ async function cmdExcel(args) {
|
|||
ws.addRow({
|
||||
number: `#${conv.number}`,
|
||||
subject: conv.subject,
|
||||
customer: conv.customer_name !== '-' ? conv.customer_name : conv.customer_email,
|
||||
company: conv.customer_company,
|
||||
customer: conv.customer,
|
||||
website: conv.website,
|
||||
hours: formatHours(conv.totalSeconds),
|
||||
});
|
||||
});
|
||||
|
||||
// Total row
|
||||
const totalRow = ws.addRow({ number: '', subject: '', customer: '', company: 'TOTAL', hours: formatHours(totalSeconds) });
|
||||
const totalRow = ws.addRow({ number: '', subject: '', customer: '', website: 'TOTAL', hours: formatHours(totalSeconds) });
|
||||
totalRow.font = { bold: true };
|
||||
|
||||
await wb.xlsx.writeFile(output);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue