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; } } };