import RipplingSDK, { DetailViewActionFunctionEvent, FunctionContext, FunctionResponse, } from '@rippling/rippling-sdk'; import type { CustomObjectDataRow } from '@rippling/rippling-sdk/resources/custom-objects/records'; const APPOINTMENT_OBJECT_API_NAME = 'dental_appointment__c'; const TREATMENT_PLAN_OBJECT_API_NAME = 'dental_treatment_plan__c'; type CompleteVisitEvent = DetailViewActionFunctionEvent< {}, typeof APPOINTMENT_OBJECT_API_NAME, 'Complete visit', 'layout_dental_appointment' >; type CompleteVisitResponse = { appointmentId: string; treatmentPlanId?: string; message: string; }; function createClient(context: FunctionContext): RipplingSDK { return new RipplingSDK({ bearerToken: context.env.rippling_user_bearer_token, }); } function stringValue(record: CustomObjectDataRow, key: string): string { const value = record[key]; return typeof value === 'string' ? value : ''; } function lookupId(record: CustomObjectDataRow, key: string): string | undefined { const value = record[key]; if (typeof value === 'string') return value; if (value == null || typeof value !== 'object') return undefined; const nested = value as Record; return typeof nested['id'] === 'string' ? nested['id'] : undefined; } function ok(body: CompleteVisitResponse): FunctionResponse { return new FunctionResponse({ body, statusCode: 200, headers: {}, message: body.message, }); } function badRequest(message: string): FunctionResponse<{ error: string }> { return new FunctionResponse({ body: { error: message }, statusCode: 400, headers: {}, message, }); } export async function onRipplingEvent( event: CompleteVisitEvent, context: FunctionContext, ): Promise> { if (event.object_api_name !== APPOINTMENT_OBJECT_API_NAME) { return badRequest('Unsupported detail view action.'); } const client = createClient(context); const appointment = await client.customObjects.records.retrieve( APPOINTMENT_OBJECT_API_NAME, event.record_id, ); if (stringValue(appointment, 'appointment_status__c') === 'Completed') { return ok({ appointmentId: event.record_id, message: `${appointment.name} is already completed.`, }); } await client.customObjects.records.update(APPOINTMENT_OBJECT_API_NAME, event.record_id, { appointment_status__c: 'Completed', visit_notes__c: stringValue(appointment, 'visit_notes__c'), }); const patientId = lookupId(appointment, 'patient_ref__c'); if (!patientId) { return ok({ appointmentId: event.record_id, message: `${appointment.name} marked completed. No treatment plan was created because the appointment has no patient.`, }); } const treatmentPlan = await client.customObjects.records.create(TREATMENT_PLAN_OBJECT_API_NAME, { name: `Follow-up: ${appointment.name}`, plan_patient_ref__c: patientId, plan_status__c: 'Draft', procedures_summary__c: stringValue(appointment, 'visit_notes__c') || 'Follow up after the completed visit.', }); const response: CompleteVisitResponse = { appointmentId: event.record_id, message: `${appointment.name} marked completed and follow-up treatment plan drafted.`, }; if (treatmentPlan.data?.id != null) response.treatmentPlanId = treatmentPlan.data.id; return ok(response); }