Repository navigation
feat: complete roadmap — Automation & Workflow + Enhanced Analytics + build fixes - #119
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…and smart delegation Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
… accuracy analytics Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
… Compliance analytics Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
… trend analysis Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
… analytics - Advanced Approvals: parallel approval chains and smart delegation workflow - HR Workflows: onboarding automation, time-off approvals, performance review cycles - Contract Renewals: auto-create renewal opportunity 90 days before expiration - Revenue Dashboards: TCV, ARR, Quote-to-Close analytics - Sales Performance: pipeline metrics, win rates, forecast accuracy - Service Metrics: First Response Time, CSAT, SLA compliance - HR Analytics: time-to-hire, attrition rates, performance trends - Updated plugin registrations and index exports - Updated roadmap documentation Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…nd service_metrics action Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
| } from '../../../src/actions/service_metrics.action'; | ||
|
|
||
| // Helper: build a date string N minutes ago | ||
| function minutesAgo(min: number): string { |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
The general fix for an unused function in a test file is to either remove it or start using it where appropriate. To avoid changing existing behavior, the safest approach is to remove the unused helper entirely, as it currently has no effect on the tests. There is no indication that future tests depend on it, and no references appear in the visible code.
Concretely, in packages/support/__tests__/unit/actions/service_metrics.action.test.ts, delete the minutesAgo function definition (lines 16–19 in the snippet), leaving the daysAgo helper and all tests unchanged. No new imports or other definitions are required.
| @@ -13,11 +13,7 @@ | ||
| analyzeSLACompliance | ||
| } from '../../../src/actions/service_metrics.action'; | ||
|
|
||
| // Helper: build a date string N minutes ago | ||
| function minutesAgo(min: number): string { | ||
| return new Date(Date.now() - min * 60 * 1000).toISOString(); | ||
| } | ||
|
|
||
| // Helper: build a date string N days ago | ||
| function daysAgo(days: number): string { | ||
| return new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); | ||
| } |
| return new Date(Date.now() - min * 60 * 1000).toISOString(); | ||
| } | ||
|
|
||
| function daysAgo(days: number): string { |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 8 months ago
In general, to fix an unused function warning, either remove the function if it truly isn’t needed, or start using it where appropriate. Since daysAgo is not referenced anywhere in the snippet and there is no sign it is required for test behavior, the safest change that preserves current functionality is to delete the unused helper.
Concretely, in packages/support/__tests__/unit/actions/service_metrics.action.test.ts, remove the daysAgo function definition at lines 21–23:
21: function daysAgo(days: number): string {
22: return new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
23: }No additional imports or definitions are needed. All other code can remain unchanged, since no other lines reference daysAgo.
| @@ -18,10 +18,6 @@ | ||
| return new Date(Date.now() - min * 60 * 1000).toISOString(); | ||
| } | ||
|
|
||
| function daysAgo(days: number): string { | ||
| return new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); | ||
| } | ||
|
|
||
| describe('analyzeFirstResponseTime', () => { | ||
| beforeEach(() => { | ||
| vi.resetAllMocks(); |
|
@copilot fix all build errors |
- Fix WorkflowRule.create() → plain object (WorkflowRule is a type, not a class) - Fix Agent.create() → plain object in all agent files - Fix ListView.create() → plain object in account.view.ts - Fix PageSchema.create() → plain object in account.page.ts - Fix Dashboard.create() → plain object in sales.dashboard.ts - Fix StateMachine → StateMachineConfig (correct export name) in case.statemachine.ts - Fix RAGPipelineConfig.create() → plain object in knowledge.rag.ts - Add ../ai reference to CRM tsconfig.json for @hotcrm/ai resolution - All 10 packages build successfully - All 496 tests passing Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Fixed all build errors in commit f06c36d. The root cause was that |
There was a problem hiding this comment.
Pull request overview
This PR implements Phase 2 roadmap items for Automation & Workflow and Enhanced Analytics across multiple HotCRM packages, and applies build fixes related to @objectstack/spec v1.1.0 types by replacing .create() usages with plain object literals.
Changes:
- Added new analytics action modules (Sales Performance, Revenue Dashboard, Service Metrics, HR Analytics) plus unit tests.
- Added new automation/workflow configs (Advanced Approvals, HR Workflows) and a new Finance contract renewal hook wired into the Finance plugin.
- Updated various UI/agent/workflow/dashboard configs to object literals for build compatibility, and marked roadmap items as completed.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/support/src/actions/service_metrics.action.ts | Adds service analytics (FRT/CSAT/SLA compliance). |
| packages/support/tests/unit/actions/service_metrics.action.test.ts | Adds unit tests for the service metrics actions. |
| packages/finance/src/hooks/contract_renewal.hook.ts | Adds renewal opportunity creation + expiration alert hooks. |
| packages/finance/tests/unit/hooks/contract_renewal.hook.test.ts | Adds unit tests for the renewal hooks. |
| packages/products/src/approval.workflow.ts | Adds advanced approval workflow rules (delegation/escalation/auto-resolve). |
| content/docs/roadmap.mdx | Marks Phase 2 items as completed. |
| packages/*/src/{agents,views,pages,workflows,dashboards}.ts | Converts @objectstack/spec .create() usage to object literals for build fixes. |
| await db.insert('task', { | ||
| subject: `Initiate renewal for Contract ${newDoc.contract_number}`, | ||
| description: `Contract ${newDoc.contract_number} expires on ${endDate.toISOString().split('T')[0]}. Please reach out to the customer to discuss renewal.`, | ||
| related_to: newDoc.account, | ||
| opportunity: opportunity._id, | ||
| status: 'Open', | ||
| priority: daysUntilExpiry <= ALERT_WINDOW_DAYS ? 'High' : 'Normal', | ||
| due_date: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString() | ||
| }); |
There was a problem hiding this comment.
The inserted task uses fields related_to and opportunity, but the CRM Task schema uses what_id / who_id for relationships and does not define an opportunity field. Update the task payload to match the Task schema (and ensure due_date is set since it’s required).
| related_to: newDoc.account, | ||
| contract: newDoc._id, |
There was a problem hiding this comment.
The inserted activity uses fields related_to and contract, but the CRM Activity schema uses what_id/who_id for associations and has no contract field. Align the activity payload with the Activity schema so the alert can be persisted and related correctly.
| related_to: newDoc.account, | |
| contract: newDoc._id, | |
| what_id: newDoc._id, | |
| who_id: newDoc.account, |
| original_approver: '${current_approver_id.delegated_from_id.name}', | ||
| delegate_approver: '${current_approver_id.name}', | ||
| reason: 'out_of_office', | ||
| timestamp: '${NOW()}' |
There was a problem hiding this comment.
In SmartDelegation, the audit webhook payload uses timestamp: '${NOW()}'. Elsewhere in workflows, NOW() is used as a formula/value string (e.g., value: 'NOW()'). If the workflow engine doesn’t template-evaluate function calls inside ${...}, this will emit a literal string; consider using a supported date expression consistently (e.g., NOW() without ${} or a dedicated runtime token).
| timestamp: '${NOW()}' | |
| timestamp: 'NOW()' |
| const cutoffDate = new Date(Date.now() - periodDays * 24 * 60 * 60 * 1000).toISOString(); | ||
| const filters: any[] = [ | ||
| ['created_date', '>', cutoffDate], | ||
| ['first_response_date', '!=', null] | ||
| ]; | ||
| if (priority) filters.push(['priority', '=', priority]); | ||
| if (channel) filters.push(['channel', '=', channel]); | ||
|
|
||
| const cases = await db.find('case', { | ||
| filters, | ||
| fields: [ | ||
| 'priority', 'channel', 'created_date', 'first_response_date', | ||
| 'status', 'owner_id' | ||
| ] | ||
| }); |
There was a problem hiding this comment.
analyzeFirstResponseTime queries and computes using first_response_date and channel, but the case schema defines first_response_time and uses origin for the channel field. As written, this will return empty/incorrect analytics against the defined object fields; align the queried fields (and response-time calculation inputs) with the Case object schema.
| { satisfaction_rating: 5, owner_id: 'agent_1', category: 'billing', priority: 'medium', created_date: new Date(now - 2 * 86400000).toISOString(), closed_date: new Date(now - 1 * 86400000).toISOString(), first_response_date: new Date(now - 1.5 * 86400000).toISOString(), status: 'Closed', is_escalated: false, resolution_type: 'first_contact', channel: 'chat' }, | ||
| { satisfaction_rating: 4, owner_id: 'agent_2', category: 'technical', priority: 'high', created_date: new Date(now - 3 * 86400000).toISOString(), closed_date: new Date(now - 2 * 86400000).toISOString(), first_response_date: new Date(now - 2.5 * 86400000).toISOString(), status: 'Closed', is_escalated: false, resolution_type: 'resolved', channel: 'email' }, | ||
| { satisfaction_rating: 2, owner_id: 'agent_1', category: 'billing', priority: 'low', created_date: new Date(now - 4 * 86400000).toISOString(), closed_date: new Date(now - 3 * 86400000).toISOString(), first_response_date: new Date(now - 3.5 * 86400000).toISOString(), status: 'Closed', is_escalated: true, resolution_type: 'resolved', channel: 'email' }, | ||
| { satisfaction_rating: 5, owner_id: 'agent_2', category: 'technical', priority: 'medium', created_date: new Date(now - 5 * 86400000).toISOString(), closed_date: new Date(now - 4 * 86400000).toISOString(), first_response_date: new Date(now - 4.5 * 86400000).toISOString(), status: 'Closed', is_escalated: false, resolution_type: 'first_contact', channel: 'chat' }, |
There was a problem hiding this comment.
Unit tests use satisfaction_rating and first_response_date fields, which do not match the case object schema (satisfaction_score, first_response_time, and origin). Once the action is aligned to schema fields, update these fixtures/expectations so tests validate the real contract.
| { satisfaction_rating: 5, owner_id: 'agent_1', category: 'billing', priority: 'medium', created_date: new Date(now - 2 * 86400000).toISOString(), closed_date: new Date(now - 1 * 86400000).toISOString(), first_response_date: new Date(now - 1.5 * 86400000).toISOString(), status: 'Closed', is_escalated: false, resolution_type: 'first_contact', channel: 'chat' }, | |
| { satisfaction_rating: 4, owner_id: 'agent_2', category: 'technical', priority: 'high', created_date: new Date(now - 3 * 86400000).toISOString(), closed_date: new Date(now - 2 * 86400000).toISOString(), first_response_date: new Date(now - 2.5 * 86400000).toISOString(), status: 'Closed', is_escalated: false, resolution_type: 'resolved', channel: 'email' }, | |
| { satisfaction_rating: 2, owner_id: 'agent_1', category: 'billing', priority: 'low', created_date: new Date(now - 4 * 86400000).toISOString(), closed_date: new Date(now - 3 * 86400000).toISOString(), first_response_date: new Date(now - 3.5 * 86400000).toISOString(), status: 'Closed', is_escalated: true, resolution_type: 'resolved', channel: 'email' }, | |
| { satisfaction_rating: 5, owner_id: 'agent_2', category: 'technical', priority: 'medium', created_date: new Date(now - 5 * 86400000).toISOString(), closed_date: new Date(now - 4 * 86400000).toISOString(), first_response_date: new Date(now - 4.5 * 86400000).toISOString(), status: 'Closed', is_escalated: false, resolution_type: 'first_contact', channel: 'chat' }, | |
| { satisfaction_score: 5, owner_id: 'agent_1', category: 'billing', priority: 'medium', created_date: new Date(now - 2 * 86400000).toISOString(), closed_date: new Date(now - 1 * 86400000).toISOString(), first_response_time: new Date(now - 1.5 * 86400000).toISOString(), status: 'Closed', is_escalated: false, resolution_type: 'first_contact', origin: 'chat' }, | |
| { satisfaction_score: 4, owner_id: 'agent_2', category: 'technical', priority: 'high', created_date: new Date(now - 3 * 86400000).toISOString(), closed_date: new Date(now - 2 * 86400000).toISOString(), first_response_time: new Date(now - 2.5 * 86400000).toISOString(), status: 'Closed', is_escalated: false, resolution_type: 'resolved', origin: 'email' }, | |
| { satisfaction_score: 2, owner_id: 'agent_1', category: 'billing', priority: 'low', created_date: new Date(now - 4 * 86400000).toISOString(), closed_date: new Date(now - 3 * 86400000).toISOString(), first_response_time: new Date(now - 3.5 * 86400000).toISOString(), status: 'Closed', is_escalated: true, resolution_type: 'resolved', origin: 'email' }, | |
| { satisfaction_score: 5, owner_id: 'agent_2', category: 'technical', priority: 'medium', created_date: new Date(now - 5 * 86400000).toISOString(), closed_date: new Date(now - 4 * 86400000).toISOString(), first_response_time: new Date(now - 4.5 * 86400000).toISOString(), status: 'Closed', is_escalated: false, resolution_type: 'first_contact', origin: 'chat' }, |
| // Fetch cases with satisfaction data | ||
| const caseFilters: any[] = [ | ||
| ['created_date', '>', cutoffDate], | ||
| ['satisfaction_rating', '!=', null] | ||
| ]; | ||
| if (category) caseFilters.push(['category', '=', category]); | ||
|
|
||
| const cases = await db.find('case', { | ||
| filters: caseFilters, | ||
| fields: [ | ||
| 'satisfaction_rating', 'owner_id', 'category', 'priority', | ||
| 'created_date', 'closed_date', 'first_response_date', 'status', | ||
| 'is_escalated', 'resolution_type', 'channel' | ||
| ] | ||
| }); |
There was a problem hiding this comment.
analyzeCSAT filters/reads satisfaction_rating, but the case object schema defines satisfaction_score (numeric 1–5) and customer_satisfaction (select). This will make CSAT/NPS analytics non-functional unless the field name is corrected and the query fields updated accordingly.
| // Fetch SLA milestones | ||
| const milestoneFilters: any[] = [ | ||
| ['created_date', '>', cutoffDate] | ||
| ]; | ||
| if (slaType) milestoneFilters.push(['type', '=', slaType]); | ||
|
|
||
| const milestones = await db.find('sla_milestone', { | ||
| filters: milestoneFilters, | ||
| fields: [ | ||
| 'type', 'target_date', 'completed_date', 'status', 'priority', | ||
| 'owner_id', 'team_id', 'target_object_id', 'breach_reason', | ||
| 'created_date' | ||
| ] | ||
| }); |
There was a problem hiding this comment.
analyzeSLACompliance queries sla_milestone using fields like type/target_date/completed_date/team_id/target_object_id/breach_reason, but the sla_milestone schema uses different field names (e.g., milestone_type, target_date_time, actual_date_time, case_id, etc.) and different status values (Completed/Violated/InProgress). This mismatch will break SLA compliance reporting; update the query + status logic to match SLAMilestone fields.
| const opportunity = await db.insert('opportunity', { | ||
| name: `Renewal: ${newDoc.contract_number}`, | ||
| account: newDoc.account, | ||
| source_contract: newDoc._id, | ||
| type: 'Renewal', | ||
| stage: 'Prospecting', | ||
| amount: newDoc.contract_value, | ||
| close_date: closeDate.toISOString() | ||
| }); |
There was a problem hiding this comment.
The inserted opportunity document uses fields account and source_contract, but the CRM Opportunity schema uses account_id and does not define source_contract. This is likely to fail schema validation or produce orphaned data; use schema-defined fields (or add the necessary lookup field to the Opportunity object) to link the renewal opp back to the contract.
| // Helper: build a date string N minutes ago | ||
| function minutesAgo(min: number): string { | ||
| return new Date(Date.now() - min * 60 * 1000).toISOString(); | ||
| } | ||
|
|
There was a problem hiding this comment.
Unused function minutesAgo.
| // Helper: build a date string N minutes ago | |
| function minutesAgo(min: number): string { | |
| return new Date(Date.now() - min * 60 * 1000).toISOString(); | |
| } |
| function daysAgo(days: number): string { | ||
| return new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); | ||
| } | ||
|
|
There was a problem hiding this comment.
Unused function daysAgo.
| function daysAgo(days: number): string { | |
| return new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); | |
| } |
Description
Implements the Phase 2 roadmap items for Automation & Workflow and Enhanced Analytics across 5 packages. Also fixes all build errors across the repository (both new and pre-existing) caused by
@objectstack/spectypes being used as classes with.create()when they are Zod-inferred types.Type of Change
Related Issues
Related to the Phase 2 roadmap in
content/docs/roadmap.mdxChanges Made
⚡ Automation & Workflow
packages/products/src/approval.workflow.ts) — 4 workflow rules: parallel approval chains, OOO smart delegation, SLA-based escalation, auto-resolve for low-risk requests (AI score < 20, discount < 5%)packages/hr/src/hr.workflow.ts) — 5 workflow rules: onboarding task creation, time-off manager routing, auto-approval for ≤1 day leaves, quarterly review cycle generation, overdue review reminderspackages/finance/src/hooks/contract_renewal.hook.ts) — 2 hooks: auto-create renewal opportunity at T-90 days, urgent expiration alert at T-30 days🎯 Enhanced Analytics
packages/finance/src/actions/revenue_dashboard.action.ts) —calculateTCV(distribution, growth trend),calculateARR(MRR normalization, new/expansion/churn movement),calculateQuoteToClose(funnel, revision analysis)packages/crm/src/actions/sales_performance.action.ts) —analyzePipeline(stage velocity, aging, health score),analyzeWinRates(by segment/source/trend),analyzeForecastAccuracy(bias, accuracy by rep/stage)packages/support/src/actions/service_metrics.action.ts) —analyzeFirstResponseTime(avg/median/P90/P95 by priority/channel),analyzeCSAT(NPS, satisfaction drivers),analyzeSLACompliance(scorecards, breach patterns)packages/hr/src/actions/hr_analytics.action.ts) —analyzeTimeToHire(stage durations, bottlenecks, cost-per-hire),analyzeAttrition(voluntary/involuntary, cohort retention, risk factors),analyzePerformanceTrends(rating distribution, flight risk)Integration
ContractRenewalCheckandContractExpirationAlerttriggers inFinancePluginproducts,hr,finance,crm,supportroadmap.mdx— marked Automation & Workflow and Enhanced Analytics sections as completed🔧 Build Fixes (14 files across 5 packages)
Fixed all build errors caused by
@objectstack/specv1.1.0 types (WorkflowRule,Agent,ListView,Dashboard,RAGPipelineConfig,StateMachineConfig) being Zod-inferred types, not classes with.create()methods:lead.workflow.ts,account.page.ts,account.view.ts,sales.dashboard.ts,sales_copilot.agent.ts,sales_assistant.agent.ts— changed.create()to plain object literals; added../aireference intsconfig.jsoncase.statemachine.ts(fixedStateMachine→StateMachineConfig),inbox.agent.ts,knowledge.rag.tsperformance_coach.agent.ts,recruitment_assistant.agent.tsapproval.workflow.tshr.workflow.tsTesting
npm test)npm run lint)npm run build)41 new tests across 3 test files:
packages/finance/__tests__/unit/hooks/contract_renewal.hook.test.ts(11 tests)packages/crm/__tests__/unit/actions/sales_performance.action.test.ts(16 tests)packages/support/__tests__/unit/actions/service_metrics.action.test.ts(14 tests)496 total tests passing (455 existing + 41 new). All 10 packages build successfully with
pnpm -r build.Screenshots
N/A — backend business logic only.
Checklist
Additional Notes
db.find()queries, in-memory calculation, default object exportType.create()— this is because@objectstack/specv1.1.0 exports these as Zod-inferred types (z.infer<typeof Schema>), not classes with factory methodsHooktype from@objectstack/spec/dataconsistent withcontract.hook.ts💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.