• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

popstas / planfix-mcp-server / 16464842119

23 Jul 2025 07:53AM UTC coverage: 81.624% (+0.7%) from 80.957%
16464842119

push

github

web-flow
Fix typecheck and update contact tests (#46)

* fix typecheck and update tests

* fix lint

435 of 599 branches covered (72.62%)

Branch coverage included in aggregate %.

6 of 6 new or added lines in 2 files covered. (100.0%)

85 existing lines in 6 files now uncovered.

3043 of 3662 relevant lines covered (83.1%)

2.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.78
/src/tools/planfix_create_lead_task.ts
1
import { z } from "zod";
1✔
2
import { PLANFIX_DRY_RUN, PLANFIX_FIELD_IDS } from "../config.js";
1✔
3
import {
1✔
4
  getTaskUrl,
5
  getToolWithHandler,
6
  log,
7
  planfixRequest,
8
} from "../helpers.js";
9
import { searchProject } from "./planfix_search_project.js";
1✔
10
import {
1✔
11
  addDirectoryEntry,
12
  addDirectoryEntries,
13
} from "../lib/planfixDirectory.js";
14
import { getTaskCustomFieldName } from "../lib/planfixCustomFields.js";
1✔
15
import { TaskRequestBody } from "../types.js";
16
import { customFieldsConfig } from "../customFieldsConfig.js";
1✔
17
import { extendSchemaWithCustomFields } from "../lib/extendSchemaWithCustomFields.js";
1✔
18
import { extendPostBodyWithCustomFields } from "../lib/extendPostBodyWithCustomFields.js";
1✔
19

20
const CreateLeadTaskInputSchemaBase = z.object({
1✔
21
  name: z.string().optional().describe("Name of the task"),
1✔
22
  description: z.string(),
1✔
23
  clientId: z.number(),
1✔
24
  managerId: z.number().optional(),
1✔
25
  agencyId: z.number().optional(),
1✔
26
  project: z.string().optional(),
1✔
27
  leadSource: z.string().optional(),
1✔
28
  pipeline: z.string().optional(),
1✔
29
  tags: z.array(z.string()).optional(),
1✔
30
  leadId: z.number().optional(),
1✔
31
});
1✔
32

33
export const CreateLeadTaskInputSchema = extendSchemaWithCustomFields(
1✔
34
  CreateLeadTaskInputSchemaBase,
1✔
35
  customFieldsConfig.leadTaskFields,
1✔
36
);
1✔
37

38
export const CreateLeadTaskOutputSchema = z.object({
1✔
39
  taskId: z.number(),
1✔
40
  url: z.string().optional(),
1✔
41
  error: z.string().optional(),
1✔
42
});
1✔
43

44
/**
45
 * Create a new lead task in Planfix
46
 * @param name - The name of the task
47
 * @param description - The description of the task
48
 * @param clientId - The ID of the client
49
 * @param managerId - Optional ID of the manager
50
 * @param agencyId - Optional ID of the agency
51
 * @param project - Optional name of the project
52
 * @param leadSource - Optional name of the lead source
53
 * @param tags - Optional array of tags
54
 * @returns Promise with the created task ID and URL
55
 */
56
export async function createLeadTask(
2✔
57
  args: z.infer<typeof CreateLeadTaskInputSchema>,
2✔
58
): Promise<{
59
  taskId: number;
60
  url?: string;
61
  error?: string;
62
}> {
2✔
63
  const {
2✔
64
    name,
2✔
65
    description,
2✔
66
    clientId,
2✔
67
    managerId,
2✔
68
    agencyId,
2✔
69
    project,
2✔
70
    leadSource,
2✔
71
    pipeline,
2✔
72
    leadId,
2✔
73
    tags,
2✔
74
  } = args;
2✔
75
  const TEMPLATE_ID = Number(process.env.PLANFIX_LEAD_TEMPLATE_ID);
2✔
76
  let finalDescription = description;
2✔
77
  let finalProjectId = 0;
2✔
78

79
  if (project) {
2✔
80
    const projectResult = await searchProject({ name: project });
1✔
81
    if (projectResult.found) {
1✔
82
      finalProjectId = projectResult.projectId;
1✔
83
    } else {
1!
UNCOV
84
      finalDescription = `${finalDescription}\nПроект: ${project}`;
×
UNCOV
85
    }
×
86
  }
1✔
87

88
  finalDescription = finalDescription.replace(/\n/g, "<br>");
2✔
89

90
  const postBody: TaskRequestBody = {
2✔
91
    template: {
2✔
92
      id: TEMPLATE_ID,
2✔
93
    },
2✔
94
    name,
2✔
95
    description: finalDescription,
2✔
96
    customFieldData: [
2✔
97
      {
2✔
98
        field: {
2✔
99
          id: PLANFIX_FIELD_IDS.client,
2✔
100
        },
2✔
101
        value: {
2✔
102
          id: clientId,
2✔
103
        },
2✔
104
      },
2✔
105
    ],
2✔
106
  };
2✔
107

108
  if (finalProjectId) {
2✔
109
    postBody.project = { id: finalProjectId };
1✔
110
  }
1✔
111

112
  if (managerId) {
2✔
113
    if (PLANFIX_FIELD_IDS.manager) {
1✔
114
      postBody.customFieldData.push({
1✔
115
        field: { id: PLANFIX_FIELD_IDS.manager },
1✔
116
        value: { id: managerId },
1✔
117
      });
1✔
118
    } else {
1!
UNCOV
119
      postBody.assignees = { users: [{ id: `user:${managerId}` }] };
×
UNCOV
120
    }
×
121
  }
1✔
122

123
  if (leadSource) {
2✔
124
    await addDirectoryEntry({
1✔
125
      objectId: TEMPLATE_ID,
1✔
126
      fieldId: PLANFIX_FIELD_IDS.leadSource,
1✔
127
      value: leadSource,
1✔
128
      postBody,
1✔
129
    });
1✔
130
  } else {
1✔
131
    const leadSourceValue = Number(
1✔
132
      process.env.PLANFIX_FIELD_ID_LEAD_SOURCE_VALUE,
1✔
133
    );
1✔
134
    if (leadSourceValue) {
1!
UNCOV
135
      postBody.customFieldData.push({
×
UNCOV
136
        field: { id: PLANFIX_FIELD_IDS.leadSource },
×
UNCOV
137
        value: { id: leadSourceValue },
×
UNCOV
138
      });
×
139
    }
×
140
  }
1✔
141

142
  if (pipeline) {
2✔
143
    await addDirectoryEntry({
1✔
144
      objectId: TEMPLATE_ID,
1✔
145
      fieldId: PLANFIX_FIELD_IDS.pipeline,
1✔
146
      value: pipeline,
1✔
147
      postBody,
1✔
148
    });
1✔
149
  }
1✔
150

151
  if (leadId && PLANFIX_FIELD_IDS.leadId) {
2!
UNCOV
152
    postBody.customFieldData.push({
×
UNCOV
153
      field: { id: PLANFIX_FIELD_IDS.leadId },
×
UNCOV
154
      value: leadId,
×
UNCOV
155
    });
×
UNCOV
156
  }
×
157

158
  if (agencyId) {
2✔
159
    postBody.customFieldData.push({
1✔
160
      field: { id: PLANFIX_FIELD_IDS.agency },
1✔
161
      value: { id: agencyId },
1✔
162
    });
1✔
163
  }
1✔
164

165
  if (tags?.length && PLANFIX_FIELD_IDS.tags && !PLANFIX_DRY_RUN) {
2✔
166
    await addDirectoryEntries({
1✔
167
      objectId: TEMPLATE_ID,
1✔
168
      fieldId: PLANFIX_FIELD_IDS.tags,
1✔
169
      values: tags,
1✔
170
      postBody,
1✔
171
    });
1✔
172
  }
1✔
173

174
  await extendPostBodyWithCustomFields(
2✔
175
    postBody,
2✔
176
    args as Record<string, unknown>,
2✔
177
    customFieldsConfig.leadTaskFields,
2✔
178
  );
2✔
179

180
  try {
2✔
181
    if (PLANFIX_DRY_RUN) {
2!
182
      const mockId = 55500000 + Math.floor(Math.random() * 10000);
×
183
      log(`[DRY RUN] Would create lead task: ${name}`);
×
184
      return { taskId: mockId, url: `https://example.com/task/${mockId}` };
×
UNCOV
185
    }
×
186

187
    const result = await planfixRequest<{ id: number }>({
2✔
188
      path: `task/`,
2✔
189
      body: postBody as unknown as Record<string, unknown>,
2✔
190
    });
2✔
191
    const taskId = result.id;
1✔
192
    const url = getTaskUrl(taskId);
1✔
193

194
    return { taskId, url };
1✔
195
  } catch (error) {
1✔
196
    let errorMessage = error instanceof Error ? error.message : "Unknown error";
1!
197
    const match = /custom_field_is_required, id (\d+)/i.exec(errorMessage);
1✔
198
    if (match) {
1✔
199
      try {
1✔
200
        const fieldId = Number(match[1]);
1✔
201
        const fieldName = await getTaskCustomFieldName(fieldId);
1✔
202
        if (fieldName) {
1✔
203
          errorMessage += `, name: ${fieldName}`;
1✔
204
        }
1✔
205
      } catch (e) {
1!
UNCOV
206
        log(
×
UNCOV
207
          `[createLeadTask] Failed to get field name: ${(e as Error).message}`,
×
UNCOV
208
        );
×
UNCOV
209
      }
×
210
    }
1✔
211
    log(`[createLeadTask] Error: ${errorMessage}`);
1✔
212
    const requestStr = JSON.stringify(postBody);
1✔
213
    return {
1✔
214
      taskId: 0,
1✔
215
      error: `Error creating task: ${errorMessage}, request: ${requestStr}`,
1✔
216
    };
1✔
217
  }
1✔
218
}
2✔
219

UNCOV
220
export async function handler(
×
UNCOV
221
  args?: Record<string, unknown>,
×
UNCOV
222
): Promise<z.infer<typeof CreateLeadTaskOutputSchema>> {
×
UNCOV
223
  const parsedArgs = CreateLeadTaskInputSchema.parse(args);
×
UNCOV
224
  return await createLeadTask(parsedArgs);
×
225
}
×
226

227
export const planfixCreateLeadTaskTool = getToolWithHandler({
1✔
228
  name: "planfix_create_lead_task",
1✔
229
  description: "Create a new lead task in Planfix",
1✔
230
  inputSchema: CreateLeadTaskInputSchema,
1✔
231
  outputSchema: CreateLeadTaskOutputSchema,
1✔
232
  handler,
1✔
233
});
1✔
234

235
export default planfixCreateLeadTaskTool;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc