/* * Grant Program Management - programs, grants, applicants, applications, milestones, and disbursements. * * gpm_program__c <- funding program and budget * gpm_grant__c <- grant cycle under a program * gpm_applicant__c <- applicant organization * gpm_application__c <- application submitted for a grant * gpm_milestone__c <- deliverable or report for an application * gpm_disbursement__c <- payment tranche for an application * * Run: * yarn tsn examples/manifest/grant-program-management/grant-program-management.ts \ * > examples/manifest/grant-program-management/manifest.json */ import { Category, CustomObject, CustomObjectFieldSection, CustomObjectPageLayout, DateField, EmailField, FormulaField, ListViewDef, LongTextField, LookupField, ManifestBuilder, NumberField, ParentChildField, Rule, SelectField, SummaryField, TextField, Validation, } from '@rippling/rippling-sdk/lib/manifest'; const manifest = new ManifestBuilder({ key: 'grant_program_management', name: 'Grant Program Management', description: 'Grant programs, application review, milestones, and disbursement tracking', }); const category = new Category(manifest, { apiName: 'grants__c', name: 'Grants', description: 'Grant program and funding records', }); const programObj = new CustomObject(manifest, { apiName: 'gpm_program__c', name: 'Program', pluralLabel: 'Programs', category, description: 'Funding program with annual budget and focus area', }); const programSection = new CustomObjectFieldSection(programObj, { name: 'Program', sectionId: 'sec_gpm_program', }); const programFocusField = new SelectField(programObj, { apiName: 'focus_area__c', displayName: 'Focus area', description: 'Program focus area', options: ['Education', 'Workforce', 'Climate', 'Health', 'Housing', 'Community safety'], required: true, section: programSection, }); const programBudgetCentsField = new NumberField(programObj, { apiName: 'annual_budget_cents__c', displayName: 'Annual budget (cents)', description: 'Annual funding budget in cents', decimalPlaces: 0, section: programSection, }); const programGuidelinesField = new LongTextField(programObj, { apiName: 'guidelines__c', displayName: 'Guidelines', description: 'Eligibility and operating guidelines', maxLength: 8000, section: programSection, }); const grantObj = new CustomObject(manifest, { apiName: 'gpm_grant__c', name: 'Grant', pluralLabel: 'Grants', category, description: 'Grant cycle under a funding program', }); const grantSection = new CustomObjectFieldSection(grantObj, { name: 'Grant', sectionId: 'sec_gpm_grant', }); const grantProgramRef = new ParentChildField(grantObj, { apiName: 'program_ref__c', displayName: 'Program', target: programObj, relatedDataLabel: 'Grants', parentChildType: 'PRIMARY', enableParentOwnerInheritance: false, description: 'Program this grant belongs to', section: grantSection, }); const grantStatusField = new SelectField(grantObj, { apiName: 'status__c', displayName: 'Status', description: 'Grant cycle status', options: ['Planning', 'Open', 'Review', 'Awarded', 'Closed'], required: true, section: grantSection, }); const grantOpenDateField = new DateField(grantObj, { apiName: 'open_date__c', displayName: 'Open date', description: 'Application open date', section: grantSection, }); const grantCloseDateField = new DateField(grantObj, { apiName: 'close_date__c', displayName: 'Close date', description: 'Application close date', section: grantSection, }); const grantAwardBudgetCentsField = new NumberField(grantObj, { apiName: 'award_budget_cents__c', displayName: 'Award budget (cents)', description: 'Budget allocated to this grant cycle', decimalPlaces: 0, section: grantSection, }); const applicantObj = new CustomObject(manifest, { apiName: 'gpm_applicant__c', name: 'Applicant', pluralLabel: 'Applicants', category, description: 'Applicant organization and contact details', }); const applicantSection = new CustomObjectFieldSection(applicantObj, { name: 'Applicant', sectionId: 'sec_gpm_applicant', }); const applicantLegalNameField = new TextField(applicantObj, { apiName: 'legal_name__c', displayName: 'Legal name', description: 'Applicant organization legal name', maxLength: 200, required: true, section: applicantSection, }); const applicantTypeField = new SelectField(applicantObj, { apiName: 'applicant_type__c', displayName: 'Applicant type', description: 'Applicant organization type', options: ['Nonprofit', 'School', 'Government', 'Small business', 'Community group'], required: true, section: applicantSection, }); const applicantEmailField = new EmailField(applicantObj, { apiName: 'contact_email__c', displayName: 'Contact email', description: 'Primary applicant contact email', section: applicantSection, }); const applicationObj = new CustomObject(manifest, { apiName: 'gpm_application__c', name: 'Application', pluralLabel: 'Applications', category, description: 'Application submitted by an applicant for a grant', }); const applicationSection = new CustomObjectFieldSection(applicationObj, { name: 'Application', sectionId: 'sec_gpm_application', }); const applicationGrantRef = new ParentChildField(applicationObj, { apiName: 'grant_ref__c', displayName: 'Grant', target: grantObj, relatedDataLabel: 'Applications', parentChildType: 'PRIMARY', enableParentOwnerInheritance: false, description: 'Grant cycle this application targets', section: applicationSection, }); const applicationApplicantRef = new LookupField(applicationObj, { apiName: 'applicant_ref__c', displayName: 'Applicant', target: applicantObj, relatedDataLabel: 'Applications', description: 'Applicant submitting this request', required: true, section: applicationSection, }); const applicationStatusField = new SelectField(applicationObj, { apiName: 'status__c', displayName: 'Status', description: 'Application review state', options: ['Draft', 'Submitted', 'Eligibility review', 'Panel review', 'Awarded', 'Declined'], required: true, section: applicationSection, }); const requestedAmountCentsField = new NumberField(applicationObj, { apiName: 'requested_amount_cents__c', displayName: 'Requested amount (cents)', description: 'Requested funding amount in cents', decimalPlaces: 0, required: true, section: applicationSection, }); const scoreField = new NumberField(applicationObj, { apiName: 'review_score__c', displayName: 'Review score', description: 'Panel score from 0 to 100', decimalPlaces: 2, section: applicationSection, }); const proposalSummaryField = new LongTextField(applicationObj, { apiName: 'proposal_summary__c', displayName: 'Proposal summary', description: 'Program plan and impact narrative', maxLength: 8000, section: applicationSection, }); const milestoneObj = new CustomObject(manifest, { apiName: 'gpm_milestone__c', name: 'Milestone', pluralLabel: 'Milestones', category, description: 'Deliverable or report due for an awarded application', }); const milestoneSection = new CustomObjectFieldSection(milestoneObj, { name: 'Milestone', sectionId: 'sec_gpm_milestone', }); const milestoneApplicationRef = new ParentChildField(milestoneObj, { apiName: 'application_ref__c', displayName: 'Application', target: applicationObj, relatedDataLabel: 'Milestones', parentChildType: 'PRIMARY', enableParentOwnerInheritance: true, description: 'Application this milestone belongs to', section: milestoneSection, }); const milestoneDueDateField = new DateField(milestoneObj, { apiName: 'due_date__c', displayName: 'Due date', description: 'Milestone due date', required: true, section: milestoneSection, }); const milestoneStatusField = new SelectField(milestoneObj, { apiName: 'status__c', displayName: 'Status', description: 'Milestone status', options: ['Not started', 'In progress', 'Submitted', 'Accepted', 'Late'], required: true, section: milestoneSection, }); const disbursementObj = new CustomObject(manifest, { apiName: 'gpm_disbursement__c', name: 'Disbursement', pluralLabel: 'Disbursements', category, description: 'Payment tranche tied to an awarded application', }); const disbursementSection = new CustomObjectFieldSection(disbursementObj, { name: 'Disbursement', sectionId: 'sec_gpm_disbursement', }); const disbursementApplicationRef = new ParentChildField(disbursementObj, { apiName: 'application_ref__c', displayName: 'Application', target: applicationObj, relatedDataLabel: 'Disbursements', parentChildType: 'PRIMARY', enableParentOwnerInheritance: true, description: 'Application funded by this disbursement', section: disbursementSection, }); const disbursementAmountCentsField = new NumberField(disbursementObj, { apiName: 'amount_cents__c', displayName: 'Amount (cents)', description: 'Disbursement amount in cents', decimalPlaces: 0, required: true, section: disbursementSection, }); const disbursementStatusField = new SelectField(disbursementObj, { apiName: 'status__c', displayName: 'Status', description: 'Disbursement status', options: ['Scheduled', 'Approved', 'Paid', 'Held', 'Cancelled'], required: true, section: disbursementSection, }); const disbursementDateField = new DateField(disbursementObj, { apiName: 'disbursement_date__c', displayName: 'Disbursement date', description: 'Scheduled or actual payment date', section: disbursementSection, }); const grantCountOnProgram = new SummaryField(programObj, { apiName: 'grant_count__c', displayName: 'Grants', aggregates: grantStatusField, using: 'COUNT', lookup: grantProgramRef, description: 'Grant cycles under this program', section: programSection, }); const applicationCountOnGrant = new SummaryField(grantObj, { apiName: 'application_count__c', displayName: 'Applications', aggregates: applicationApplicantRef, using: 'COUNT', lookup: applicationGrantRef, description: 'Applications submitted for this grant', section: grantSection, }); const requestedAmountOnGrant = new SummaryField(grantObj, { apiName: 'requested_amount_cents_total__c', displayName: 'Requested amount total (cents)', aggregates: requestedAmountCentsField, using: 'SUM', lookup: applicationGrantRef, description: 'Total requested funding for this grant', section: grantSection, }); const milestoneCountOnApplication = new SummaryField(applicationObj, { apiName: 'milestone_count__c', displayName: 'Milestones', aggregates: milestoneStatusField, using: 'COUNT', lookup: milestoneApplicationRef, description: 'Milestones attached to this application', section: applicationSection, }); const disbursedAmountOnApplication = new SummaryField(applicationObj, { apiName: 'disbursed_amount_cents__c', displayName: 'Disbursed amount (cents)', aggregates: disbursementAmountCentsField, using: 'SUM', lookup: disbursementApplicationRef, description: 'Total disbursements for this application', section: applicationSection, }); const applicationLabelField = new FormulaField(applicationObj, { apiName: 'review_label__c', displayName: 'Review label', description: 'Review queue label', fieldType: 'TEXT', maxLength: 240, formula: "CONCATENATE(status__c, ' - score ', review_score__c)", section: applicationSection, }); new Validation(grantObj, { ruleId: 'gpm_grant_close_after_open', displayName: 'Close date must be after open date', rqlFormula: '(close_date__c <= open_date__c)', errorMessage: 'Close date must be after open date.', ruleDescription: 'Prevents inverted application windows', state: 'ACTIVE', level: 'FIELD', fieldRef: grantCloseDateField, }); new Validation(applicationObj, { ruleId: 'gpm_requested_amount_positive', displayName: 'Requested amount must be positive', rqlFormula: '(requested_amount_cents__c <= 0)', errorMessage: 'Requested amount must be greater than zero.', ruleDescription: 'Blocks zero-value applications', state: 'ACTIVE', level: 'FIELD', fieldRef: requestedAmountCentsField, }); new Validation(applicationObj, { ruleId: 'gpm_review_score_range', displayName: 'Review score must be 0-100', rqlFormula: '((review_score__c < 0) OR (review_score__c > 100))', errorMessage: 'Review score must be between 0 and 100.', ruleDescription: 'Keeps panel scores in range', state: 'ACTIVE', level: 'FIELD', fieldRef: scoreField, }); new Rule(applicationObj, { flowId: 'gpm_default_application_status', flowName: 'Default application status', triggerType: 'CREATE', description: 'New applications start as Draft', actions: [{ targetField: applicationStatusField, fixedValue: ['Draft'] }], }); new Rule(milestoneObj, { flowId: 'gpm_default_milestone_status', flowName: 'Default milestone status', triggerType: 'CREATE', description: 'New milestones start Not started', actions: [{ targetField: milestoneStatusField, fixedValue: ['Not started'] }], }); new ListViewDef(programObj, { viewId: 'view_gpm_programs', name: 'Programs', description: 'Funding programs with budgets and grant counts', fields: ['name', programFocusField, programBudgetCentsField, grantCountOnProgram], orderBy: 'name', sortOrder: 'ASC', isPublic: true, objectScope: 'all', }); new ListViewDef(grantObj, { viewId: 'view_gpm_grants', name: 'Grants', description: 'Grant cycles with application and requested amount rollups', fields: [ 'name', grantProgramRef, grantStatusField, grantOpenDateField, grantCloseDateField, grantAwardBudgetCentsField, applicationCountOnGrant, requestedAmountOnGrant, ], orderBy: grantCloseDateField, sortOrder: 'ASC', isPublic: true, objectScope: 'all', }); new ListViewDef(applicantObj, { viewId: 'view_gpm_applicants', name: 'Applicants', description: 'Applicant directory', fields: ['name', applicantLegalNameField, applicantTypeField, applicantEmailField], orderBy: applicantLegalNameField, sortOrder: 'ASC', isPublic: true, objectScope: 'all', }); new ListViewDef(applicationObj, { viewId: 'view_gpm_applications', name: 'Applications', description: 'Application review queue', fields: [ 'name', applicationGrantRef, applicationApplicantRef, applicationStatusField, requestedAmountCentsField, scoreField, milestoneCountOnApplication, disbursedAmountOnApplication, applicationLabelField, ], orderBy: scoreField, sortOrder: 'DESC', isPublic: true, objectScope: 'all', }); new ListViewDef(milestoneObj, { viewId: 'view_gpm_milestones', name: 'Milestones', description: 'Deliverables and report deadlines', fields: ['name', milestoneApplicationRef, milestoneDueDateField, milestoneStatusField], orderBy: milestoneDueDateField, sortOrder: 'ASC', isPublic: true, objectScope: 'all', }); new ListViewDef(disbursementObj, { viewId: 'view_gpm_disbursements', name: 'Disbursements', description: 'Payment tranches by application', fields: [ 'name', disbursementApplicationRef, disbursementAmountCentsField, disbursementStatusField, disbursementDateField, ], orderBy: disbursementDateField, sortOrder: 'ASC', isPublic: true, objectScope: 'all', }); CustomObjectPageLayout.basic(programObj, { section: programSection, fields: ['name', programFocusField, programBudgetCentsField, grantCountOnProgram, programGuidelinesField], }); CustomObjectPageLayout.basic(grantObj, { section: grantSection, fields: [ 'name', grantProgramRef, grantStatusField, grantOpenDateField, grantCloseDateField, grantAwardBudgetCentsField, applicationCountOnGrant, requestedAmountOnGrant, ], }); CustomObjectPageLayout.basic(applicantObj, { section: applicantSection, fields: ['name', applicantLegalNameField, applicantTypeField, applicantEmailField], }); CustomObjectPageLayout.basic(applicationObj, { section: applicationSection, fields: [ 'name', applicationGrantRef, applicationApplicantRef, applicationStatusField, requestedAmountCentsField, scoreField, milestoneCountOnApplication, disbursedAmountOnApplication, applicationLabelField, proposalSummaryField, ], }); CustomObjectPageLayout.basic(milestoneObj, { section: milestoneSection, fields: ['name', milestoneApplicationRef, milestoneDueDateField, milestoneStatusField], }); CustomObjectPageLayout.basic(disbursementObj, { section: disbursementSection, fields: [ 'name', disbursementApplicationRef, disbursementAmountCentsField, disbursementStatusField, disbursementDateField, ], }); console.log(manifest.preview());