115 lines
3.0 KiB
JavaScript
115 lines
3.0 KiB
JavaScript
const db = require('../db/models');
|
|
const ActivitiesDBApi = require('../db/api/activities');
|
|
|
|
const ACTIVITY_TYPES = new Set(['call', 'email', 'meeting', 'task', 'demo', 'note']);
|
|
|
|
function httpError(message, code = 400) {
|
|
const error = new Error(message);
|
|
error.code = code;
|
|
return error;
|
|
}
|
|
|
|
function cleanText(value) {
|
|
return typeof value === 'string' ? value.trim() : '';
|
|
}
|
|
|
|
function parseRequiredDate(value, fieldName) {
|
|
const date = new Date(value);
|
|
|
|
if (!value || Number.isNaN(date.getTime())) {
|
|
throw httpError(`${fieldName} must be a valid date and time.`);
|
|
}
|
|
|
|
return date;
|
|
}
|
|
|
|
module.exports = class SalesWorkflowService {
|
|
static async scheduleFollowUp(data, currentUser) {
|
|
if (!currentUser || !currentUser.id) {
|
|
throw httpError('You must be signed in to schedule a follow-up.', 403);
|
|
}
|
|
|
|
const dealId = cleanText(data?.dealId || data?.deal);
|
|
const activityType = cleanText(data?.activity_type) || 'task';
|
|
const subject = cleanText(data?.subject);
|
|
const details = cleanText(data?.details);
|
|
const dueAt = parseRequiredDate(data?.due_at, 'Follow-up time');
|
|
const reminderAt = data?.reminder_at
|
|
? parseRequiredDate(data.reminder_at, 'Reminder time')
|
|
: new Date(dueAt.getTime() - 30 * 60 * 1000);
|
|
|
|
if (!dealId) {
|
|
throw httpError('A deal is required before scheduling a follow-up.');
|
|
}
|
|
|
|
if (!ACTIVITY_TYPES.has(activityType)) {
|
|
throw httpError('Activity type must be call, email, meeting, task, demo, or note.');
|
|
}
|
|
|
|
if (!subject) {
|
|
throw httpError('Subject is required before scheduling a follow-up.');
|
|
}
|
|
|
|
const transaction = await db.sequelize.transaction();
|
|
|
|
try {
|
|
const deal = await db.deals.findByPk(dealId, { transaction });
|
|
|
|
if (!deal) {
|
|
throw httpError('Deal not found.', 404);
|
|
}
|
|
|
|
const activityPayload = {
|
|
activity_type: activityType,
|
|
subject,
|
|
details,
|
|
scheduled_at: dueAt,
|
|
due_at: dueAt,
|
|
status: 'planned',
|
|
is_reminder_enabled: data?.is_reminder_enabled !== false,
|
|
reminder_at: reminderAt,
|
|
assigned_to: currentUser.id,
|
|
deal: deal.id,
|
|
lead: deal.leadId || null,
|
|
contact: deal.primary_contactId || null,
|
|
};
|
|
|
|
const activity = await ActivitiesDBApi.create(
|
|
activityPayload,
|
|
{
|
|
currentUser,
|
|
transaction,
|
|
},
|
|
);
|
|
|
|
await deal.update(
|
|
{
|
|
next_follow_up_at: dueAt,
|
|
updatedById: currentUser.id,
|
|
},
|
|
{ transaction },
|
|
);
|
|
|
|
await transaction.commit();
|
|
|
|
return {
|
|
activity: {
|
|
id: activity.id,
|
|
activity_type: activity.activity_type,
|
|
subject: activity.subject,
|
|
due_at: activity.due_at,
|
|
status: activity.status,
|
|
},
|
|
deal: {
|
|
id: deal.id,
|
|
name: deal.name,
|
|
next_follow_up_at: deal.next_follow_up_at,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
throw error;
|
|
}
|
|
}
|
|
};
|