@automate.ax/integration-contracts 0.90.0 → 0.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jobnimbus/api.d.ts +71 -0
- package/dist/jobnimbus/api.js +231 -0
- package/dist/jobnimbus/index.d.ts +2 -0
- package/dist/jobnimbus/index.js +2 -0
- package/dist/jobnimbus/schemas.d.ts +508 -0
- package/dist/jobnimbus/schemas.js +308 -0
- package/dist/whatsapp/index.d.ts +2 -2
- package/dist/whatsapp/schemas.d.ts +5 -5
- package/package.json +8 -2
- package/src/jobnimbus/api.ts +294 -0
- package/src/jobnimbus/index.ts +2 -0
- package/src/jobnimbus/schemas.ts +351 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { isEncodable, type Encodable } from "@automate.ax/codec"
|
|
2
|
+
import * as z from "zod"
|
|
3
|
+
|
|
4
|
+
const JOB_NIMBUS_API_ORIGINS = {
|
|
5
|
+
accountsReceivable: "https://api.jobnimbus.com/accounts-receivable/v1/",
|
|
6
|
+
activities: "https://api.jobnimbus.com/activities/v1/",
|
|
7
|
+
files: "https://api.jobnimbus.com/files/v1/",
|
|
8
|
+
identity: "https://api.jobnimbus.com/identity/v1/",
|
|
9
|
+
public: "https://app.jobnimbus.com/api1/",
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
const JOB_NIMBUS_SECRET_SCHEMA = z.object({
|
|
13
|
+
apiKey: z.string().trim().min(1),
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const JOB_NIMBUS_VALUE_SCHEMA = z.custom<Encodable>(isEncodable, {
|
|
17
|
+
message: "Expected a codec-safe JobNimbus value.",
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export const JOB_NIMBUS_RESPONSE_SCHEMA = z.union([
|
|
21
|
+
JOB_NIMBUS_VALUE_SCHEMA,
|
|
22
|
+
z.undefined().transform(() => null),
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
export type JobNimbusApi = keyof typeof JOB_NIMBUS_API_ORIGINS
|
|
26
|
+
|
|
27
|
+
interface JobNimbusRequestOptions<TSchema extends z.ZodType> {
|
|
28
|
+
body?: Encodable
|
|
29
|
+
contentType?: string
|
|
30
|
+
headers?: Record<string, string | undefined>
|
|
31
|
+
method?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT"
|
|
32
|
+
query?: Record<
|
|
33
|
+
string,
|
|
34
|
+
| boolean
|
|
35
|
+
| number
|
|
36
|
+
| string
|
|
37
|
+
| readonly (boolean | number | string)[]
|
|
38
|
+
| null
|
|
39
|
+
| undefined
|
|
40
|
+
>
|
|
41
|
+
responseSchema: TSchema
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Error returned by a rejected JobNimbus API request. */
|
|
45
|
+
export class JobNimbusApiError extends Error {
|
|
46
|
+
readonly details: Encodable
|
|
47
|
+
readonly status: number
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates an error from a rejected JobNimbus response.
|
|
51
|
+
*
|
|
52
|
+
* @param status - HTTP response status.
|
|
53
|
+
* @param details - Codec-safe response details.
|
|
54
|
+
*/
|
|
55
|
+
constructor(status: number, details: Encodable) {
|
|
56
|
+
super(`JobNimbus API request failed (${status}).`)
|
|
57
|
+
this.name = "JobNimbusApiError"
|
|
58
|
+
this.details = details
|
|
59
|
+
this.status = status
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates an authenticated client for one documented JobNimbus API root.
|
|
65
|
+
*
|
|
66
|
+
* @param secret - Stored JobNimbus API key.
|
|
67
|
+
* @param api - Public API or one current Platform service.
|
|
68
|
+
*/
|
|
69
|
+
export function getJobNimbusApi(secret: unknown, api: JobNimbusApi) {
|
|
70
|
+
const { apiKey } = JOB_NIMBUS_SECRET_SCHEMA.parse(secret)
|
|
71
|
+
const origin = JOB_NIMBUS_API_ORIGINS[api]
|
|
72
|
+
const root = new URL(origin)
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
/**
|
|
76
|
+
* Sends one JSON request below the selected JobNimbus API root.
|
|
77
|
+
*
|
|
78
|
+
* @param path - Relative path below the selected root.
|
|
79
|
+
* @param options - Request method, query, body, headers, and response
|
|
80
|
+
* schema.
|
|
81
|
+
*/
|
|
82
|
+
async request<TSchema extends z.ZodType>(
|
|
83
|
+
path: string,
|
|
84
|
+
options: JobNimbusRequestOptions<TSchema>,
|
|
85
|
+
): Promise<z.output<TSchema>> {
|
|
86
|
+
const normalizedPath = path.replace(/^\/+/, "")
|
|
87
|
+
if (
|
|
88
|
+
!normalizedPath ||
|
|
89
|
+
normalizedPath.includes("://") ||
|
|
90
|
+
normalizedPath.includes("\\")
|
|
91
|
+
) {
|
|
92
|
+
throw new TypeError("JobNimbus API paths must be relative.")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const url = new URL(normalizedPath, origin)
|
|
96
|
+
if (
|
|
97
|
+
url.origin !== root.origin ||
|
|
98
|
+
!url.pathname.startsWith(root.pathname)
|
|
99
|
+
) {
|
|
100
|
+
throw new TypeError(
|
|
101
|
+
"JobNimbus API paths must remain below the selected root.",
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
for (const [key, value] of Object.entries(options.query ?? {})) {
|
|
105
|
+
if (value == null) continue
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
for (const item of value) url.searchParams.append(key, String(item))
|
|
108
|
+
} else {
|
|
109
|
+
url.searchParams.set(key, String(value))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const headers = new Headers({
|
|
114
|
+
Accept: "application/json",
|
|
115
|
+
Authorization: `Bearer ${apiKey}`,
|
|
116
|
+
})
|
|
117
|
+
for (const [key, value] of Object.entries(options.headers ?? {})) {
|
|
118
|
+
if (value !== undefined) headers.set(key, value)
|
|
119
|
+
}
|
|
120
|
+
if (options.body !== undefined) {
|
|
121
|
+
headers.set("Content-Type", options.contentType ?? "application/json")
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const response = await fetch(url, {
|
|
125
|
+
body:
|
|
126
|
+
options.body === undefined
|
|
127
|
+
? undefined
|
|
128
|
+
: JSON.stringify(
|
|
129
|
+
api === "public" ? toJobNimbus(options.body) : options.body,
|
|
130
|
+
),
|
|
131
|
+
headers,
|
|
132
|
+
method: options.method ?? "GET",
|
|
133
|
+
})
|
|
134
|
+
const text = await response.text()
|
|
135
|
+
const parsed = JOB_NIMBUS_RESPONSE_SCHEMA.safeParse(
|
|
136
|
+
fromJobNimbus(text ? parseJson(text) : null),
|
|
137
|
+
)
|
|
138
|
+
if (!response.ok || !parsed.success) {
|
|
139
|
+
throw new JobNimbusApiError(
|
|
140
|
+
response.status,
|
|
141
|
+
parsed.success ? parsed.data : { response: text },
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
return options.responseSchema.parse(parsed.data)
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Downloads one JobNimbus attachment from its documented non-API route.
|
|
151
|
+
*
|
|
152
|
+
* @param secret - Stored JobNimbus API key.
|
|
153
|
+
* @param fileId - Attachment JNID.
|
|
154
|
+
*/
|
|
155
|
+
export async function downloadJobNimbusFile(secret: unknown, fileId: string) {
|
|
156
|
+
const { apiKey } = JOB_NIMBUS_SECRET_SCHEMA.parse(secret)
|
|
157
|
+
const response = await fetch(
|
|
158
|
+
new URL(
|
|
159
|
+
encodeURIComponent(z.string().trim().min(1).parse(fileId)),
|
|
160
|
+
"https://app.jobnimbus.com/files/",
|
|
161
|
+
),
|
|
162
|
+
{ headers: { Authorization: `Bearer ${apiKey}` } },
|
|
163
|
+
)
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
throw new JobNimbusApiError(response.status, {
|
|
166
|
+
response: await response.text(),
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
return response.blob()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Converts public camelCase JSON into JobNimbus's provider-native keys.
|
|
174
|
+
*
|
|
175
|
+
* @param value - Codec-safe public value.
|
|
176
|
+
*/
|
|
177
|
+
export function toJobNimbus(value: Encodable): Encodable {
|
|
178
|
+
if (value instanceof Date) return value.toISOString()
|
|
179
|
+
if (Array.isArray(value)) return value.map(toJobNimbus)
|
|
180
|
+
if (!isPlainObject(value)) return value
|
|
181
|
+
|
|
182
|
+
return Object.fromEntries(
|
|
183
|
+
Object.entries(value).flatMap(([key, item]) =>
|
|
184
|
+
key === "customFields" && isPlainObject(item)
|
|
185
|
+
? Object.entries(item).map(([field, fieldValue]) => [
|
|
186
|
+
field,
|
|
187
|
+
toJobNimbus(fieldValue),
|
|
188
|
+
])
|
|
189
|
+
: [[toSnakeCase(key), toJobNimbus(item)]],
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Converts JobNimbus JSON into public camelCase values.
|
|
196
|
+
*
|
|
197
|
+
* Documented legacy Unix date fields become Date instances. Unknown fields are
|
|
198
|
+
* preserved so account-defined CRM fields survive normalization.
|
|
199
|
+
*
|
|
200
|
+
* @param value - Raw provider value.
|
|
201
|
+
*/
|
|
202
|
+
export function fromJobNimbus(value: unknown): Encodable {
|
|
203
|
+
return normalizeJobNimbusValue(value)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Recursively normalizes one raw provider value and its original key.
|
|
208
|
+
*
|
|
209
|
+
* @param value - Raw provider value.
|
|
210
|
+
* @param providerKey - Original provider field name, when nested in an object.
|
|
211
|
+
*/
|
|
212
|
+
function normalizeJobNimbusValue(
|
|
213
|
+
value: unknown,
|
|
214
|
+
providerKey?: string,
|
|
215
|
+
): Encodable {
|
|
216
|
+
if (typeof value === "number" && providerKey?.startsWith("date_")) {
|
|
217
|
+
return new Date(value * 1_000)
|
|
218
|
+
}
|
|
219
|
+
if (Array.isArray(value)) {
|
|
220
|
+
return value.map((item) => normalizeJobNimbusValue(item))
|
|
221
|
+
}
|
|
222
|
+
if (!isPlainObject(value)) return JOB_NIMBUS_VALUE_SCHEMA.parse(value)
|
|
223
|
+
|
|
224
|
+
const customFields: Record<string, Encodable> = {}
|
|
225
|
+
return Object.fromEntries([
|
|
226
|
+
...Object.entries(value).flatMap(([key, item]) => {
|
|
227
|
+
if (/^cf_(?:boolean|date|double|long|string)_\d+$/.test(key)) {
|
|
228
|
+
customFields[key] = normalizeJobNimbusValue(item, key)
|
|
229
|
+
return []
|
|
230
|
+
}
|
|
231
|
+
return [
|
|
232
|
+
[
|
|
233
|
+
key === "extermal_id"
|
|
234
|
+
? "externalId"
|
|
235
|
+
: key === "jnid"
|
|
236
|
+
? "id"
|
|
237
|
+
: key === "recid"
|
|
238
|
+
? "recordId"
|
|
239
|
+
: toCamelCase(key),
|
|
240
|
+
normalizeJobNimbusValue(item, key),
|
|
241
|
+
] as const,
|
|
242
|
+
]
|
|
243
|
+
}),
|
|
244
|
+
...(Object.keys(customFields).length > 0
|
|
245
|
+
? [["customFields", customFields] as const]
|
|
246
|
+
: []),
|
|
247
|
+
])
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Parses a response body while preserving non-JSON diagnostics.
|
|
252
|
+
*
|
|
253
|
+
* @param value - Raw response body.
|
|
254
|
+
*/
|
|
255
|
+
function parseJson(value: string): unknown {
|
|
256
|
+
try {
|
|
257
|
+
return JSON.parse(value)
|
|
258
|
+
} catch {
|
|
259
|
+
return { response: value }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Checks whether a value can be traversed as a JSON-style object.
|
|
265
|
+
*
|
|
266
|
+
* @param value - Candidate object value.
|
|
267
|
+
*/
|
|
268
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
269
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Converts one provider snake-case key to camel case.
|
|
274
|
+
*
|
|
275
|
+
* @param value - Provider field name.
|
|
276
|
+
*/
|
|
277
|
+
function toCamelCase(value: string) {
|
|
278
|
+
return value
|
|
279
|
+
.replace(/_([a-z0-9])/g, (_match, character: string) =>
|
|
280
|
+
character.toUpperCase(),
|
|
281
|
+
)
|
|
282
|
+
.replace(/^[A-Z]/, (character) => character.toLowerCase())
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Converts one public camel-case key to provider snake case.
|
|
287
|
+
*
|
|
288
|
+
* @param value - Public field name.
|
|
289
|
+
*/
|
|
290
|
+
function toSnakeCase(value: string) {
|
|
291
|
+
return value
|
|
292
|
+
.replace(/([a-zA-Z])([0-9])/g, "$1_$2")
|
|
293
|
+
.replace(/[A-Z]/g, (character) => `_${character.toLowerCase()}`)
|
|
294
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import * as z from "zod"
|
|
2
|
+
|
|
3
|
+
import { JOB_NIMBUS_VALUE_SCHEMA } from "./api"
|
|
4
|
+
|
|
5
|
+
export const JOB_NIMBUS_ID_SCHEMA = z.string().trim().min(1)
|
|
6
|
+
|
|
7
|
+
const NULLABLE_STRING_SCHEMA = z.string().nullable().optional()
|
|
8
|
+
const NULLABLE_NUMBER_SCHEMA = z.number().nullable().optional()
|
|
9
|
+
const DATE_SCHEMA = z.date().nullable().optional()
|
|
10
|
+
|
|
11
|
+
export const JOB_NIMBUS_CUSTOM_FIELDS_SCHEMA = z.record(
|
|
12
|
+
z.string().min(1),
|
|
13
|
+
z.union([z.boolean(), z.number(), z.string(), z.null()]),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
export const JOB_NIMBUS_REFERENCE_SCHEMA = z
|
|
17
|
+
.object({
|
|
18
|
+
id: JOB_NIMBUS_ID_SCHEMA,
|
|
19
|
+
name: NULLABLE_STRING_SCHEMA,
|
|
20
|
+
number: NULLABLE_STRING_SCHEMA,
|
|
21
|
+
type: NULLABLE_STRING_SCHEMA,
|
|
22
|
+
})
|
|
23
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
24
|
+
|
|
25
|
+
export const JOB_NIMBUS_OWNER_SCHEMA = z
|
|
26
|
+
.object({
|
|
27
|
+
id: JOB_NIMBUS_ID_SCHEMA,
|
|
28
|
+
})
|
|
29
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
30
|
+
|
|
31
|
+
export const JOB_NIMBUS_LOCATION_SCHEMA = z
|
|
32
|
+
.object({
|
|
33
|
+
id: z.number(),
|
|
34
|
+
name: NULLABLE_STRING_SCHEMA,
|
|
35
|
+
parentId: z.number().nullable().optional(),
|
|
36
|
+
})
|
|
37
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
38
|
+
|
|
39
|
+
export const JOB_NIMBUS_GEO_SCHEMA = z.object({
|
|
40
|
+
lat: z.number(),
|
|
41
|
+
lon: z.number(),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const CRM_BASE_FIELDS = {
|
|
45
|
+
dateCreated: DATE_SCHEMA,
|
|
46
|
+
createdBy: NULLABLE_STRING_SCHEMA,
|
|
47
|
+
createdByName: NULLABLE_STRING_SCHEMA,
|
|
48
|
+
customer: NULLABLE_STRING_SCHEMA,
|
|
49
|
+
customFields: JOB_NIMBUS_CUSTOM_FIELDS_SCHEMA.optional(),
|
|
50
|
+
id: JOB_NIMBUS_ID_SCHEMA,
|
|
51
|
+
isActive: z.boolean().optional(),
|
|
52
|
+
isArchived: z.boolean().optional(),
|
|
53
|
+
location: JOB_NIMBUS_LOCATION_SCHEMA.nullable().optional(),
|
|
54
|
+
owners: JOB_NIMBUS_OWNER_SCHEMA.array().optional(),
|
|
55
|
+
recordId: z.number().optional(),
|
|
56
|
+
recordType: NULLABLE_NUMBER_SCHEMA,
|
|
57
|
+
recordTypeName: NULLABLE_STRING_SCHEMA,
|
|
58
|
+
related: JOB_NIMBUS_REFERENCE_SCHEMA.array().optional(),
|
|
59
|
+
tags: z.string().array().optional(),
|
|
60
|
+
type: NULLABLE_STRING_SCHEMA,
|
|
61
|
+
dateUpdated: DATE_SCHEMA,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ADDRESS_FIELDS = {
|
|
65
|
+
addressLine1: NULLABLE_STRING_SCHEMA,
|
|
66
|
+
addressLine2: NULLABLE_STRING_SCHEMA,
|
|
67
|
+
city: NULLABLE_STRING_SCHEMA,
|
|
68
|
+
countryName: NULLABLE_STRING_SCHEMA,
|
|
69
|
+
stateText: NULLABLE_STRING_SCHEMA,
|
|
70
|
+
zip: NULLABLE_STRING_SCHEMA,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const WORKFLOW_FIELDS = {
|
|
74
|
+
isClosed: z.boolean().optional(),
|
|
75
|
+
isLead: z.boolean().optional(),
|
|
76
|
+
salesRep: NULLABLE_STRING_SCHEMA,
|
|
77
|
+
salesRepName: NULLABLE_STRING_SCHEMA,
|
|
78
|
+
source: NULLABLE_NUMBER_SCHEMA,
|
|
79
|
+
sourceName: NULLABLE_STRING_SCHEMA,
|
|
80
|
+
status: NULLABLE_NUMBER_SCHEMA,
|
|
81
|
+
statusName: NULLABLE_STRING_SCHEMA,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const JOB_NIMBUS_CONTACT_SCHEMA = z
|
|
85
|
+
.object({
|
|
86
|
+
...CRM_BASE_FIELDS,
|
|
87
|
+
...ADDRESS_FIELDS,
|
|
88
|
+
...WORKFLOW_FIELDS,
|
|
89
|
+
company: NULLABLE_STRING_SCHEMA,
|
|
90
|
+
description: NULLABLE_STRING_SCHEMA,
|
|
91
|
+
displayName: NULLABLE_STRING_SCHEMA,
|
|
92
|
+
email: NULLABLE_STRING_SCHEMA,
|
|
93
|
+
faxNumber: NULLABLE_STRING_SCHEMA,
|
|
94
|
+
firstName: NULLABLE_STRING_SCHEMA,
|
|
95
|
+
geo: JOB_NIMBUS_GEO_SCHEMA.nullable().optional(),
|
|
96
|
+
homePhone: NULLABLE_STRING_SCHEMA,
|
|
97
|
+
lastName: NULLABLE_STRING_SCHEMA,
|
|
98
|
+
mobilePhone: NULLABLE_STRING_SCHEMA,
|
|
99
|
+
website: NULLABLE_STRING_SCHEMA,
|
|
100
|
+
workPhone: NULLABLE_STRING_SCHEMA,
|
|
101
|
+
})
|
|
102
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
103
|
+
|
|
104
|
+
export const JOB_NIMBUS_JOB_SCHEMA = z
|
|
105
|
+
.object({
|
|
106
|
+
...CRM_BASE_FIELDS,
|
|
107
|
+
...ADDRESS_FIELDS,
|
|
108
|
+
...WORKFLOW_FIELDS,
|
|
109
|
+
description: NULLABLE_STRING_SCHEMA,
|
|
110
|
+
geo: JOB_NIMBUS_GEO_SCHEMA.nullable().optional(),
|
|
111
|
+
name: z.string(),
|
|
112
|
+
number: NULLABLE_STRING_SCHEMA,
|
|
113
|
+
primary: JOB_NIMBUS_REFERENCE_SCHEMA.nullable().optional(),
|
|
114
|
+
})
|
|
115
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
116
|
+
|
|
117
|
+
export const JOB_NIMBUS_TASK_SCHEMA = z
|
|
118
|
+
.object({
|
|
119
|
+
...CRM_BASE_FIELDS,
|
|
120
|
+
actualTime: NULLABLE_NUMBER_SCHEMA,
|
|
121
|
+
dateEnd: DATE_SCHEMA,
|
|
122
|
+
dateStart: DATE_SCHEMA,
|
|
123
|
+
description: NULLABLE_STRING_SCHEMA,
|
|
124
|
+
estimatedTime: NULLABLE_NUMBER_SCHEMA,
|
|
125
|
+
isCompleted: z.boolean().optional(),
|
|
126
|
+
number: NULLABLE_STRING_SCHEMA,
|
|
127
|
+
priority: NULLABLE_NUMBER_SCHEMA,
|
|
128
|
+
primary: JOB_NIMBUS_REFERENCE_SCHEMA.nullable().optional(),
|
|
129
|
+
title: z.string(),
|
|
130
|
+
})
|
|
131
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
132
|
+
|
|
133
|
+
export const JOB_NIMBUS_PRODUCT_UOM_SCHEMA = z
|
|
134
|
+
.object({
|
|
135
|
+
labor: z
|
|
136
|
+
.object({ cost: z.number().optional(), price: z.number().optional() })
|
|
137
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
138
|
+
.optional(),
|
|
139
|
+
material: z
|
|
140
|
+
.object({ cost: z.number().optional(), price: z.number().optional() })
|
|
141
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
142
|
+
.optional(),
|
|
143
|
+
uom: z.string(),
|
|
144
|
+
})
|
|
145
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
146
|
+
|
|
147
|
+
export const JOB_NIMBUS_PRODUCT_SCHEMA = z
|
|
148
|
+
.object({
|
|
149
|
+
...CRM_BASE_FIELDS,
|
|
150
|
+
description: NULLABLE_STRING_SCHEMA,
|
|
151
|
+
externalId: NULLABLE_STRING_SCHEMA,
|
|
152
|
+
itemType: z.enum(["labor", "labor+material", "material"]).optional(),
|
|
153
|
+
locationId: z.number().nullable().optional(),
|
|
154
|
+
name: z.string(),
|
|
155
|
+
suppliers: z.array(JOB_NIMBUS_VALUE_SCHEMA).optional(),
|
|
156
|
+
taxExempt: z.boolean().optional(),
|
|
157
|
+
uoms: JOB_NIMBUS_PRODUCT_UOM_SCHEMA.array().optional(),
|
|
158
|
+
})
|
|
159
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
160
|
+
|
|
161
|
+
export const JOB_NIMBUS_FILE_SCHEMA = z
|
|
162
|
+
.object({
|
|
163
|
+
...CRM_BASE_FIELDS,
|
|
164
|
+
description: NULLABLE_STRING_SCHEMA,
|
|
165
|
+
filename: NULLABLE_STRING_SCHEMA,
|
|
166
|
+
isPrivate: z.boolean().optional(),
|
|
167
|
+
name: NULLABLE_STRING_SCHEMA,
|
|
168
|
+
size: NULLABLE_NUMBER_SCHEMA,
|
|
169
|
+
})
|
|
170
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Wraps a legacy item schema in JobNimbus's offset-page response.
|
|
174
|
+
*
|
|
175
|
+
* @param itemSchema - Schema for one legacy result.
|
|
176
|
+
*/
|
|
177
|
+
export function jobNimbusPageSchema<TSchema extends z.ZodType>(
|
|
178
|
+
itemSchema: TSchema,
|
|
179
|
+
) {
|
|
180
|
+
return z
|
|
181
|
+
.object({
|
|
182
|
+
count: z.number().int().nonnegative(),
|
|
183
|
+
results: itemSchema.array(),
|
|
184
|
+
})
|
|
185
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export const JOB_NIMBUS_ACTIVITY_RECORD_INPUT_SCHEMA = z.object({
|
|
189
|
+
id: JOB_NIMBUS_ID_SCHEMA,
|
|
190
|
+
type: z.string().trim().min(1),
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
export const JOB_NIMBUS_ACTIVITY_RECORD_SCHEMA = z.object({
|
|
194
|
+
...JOB_NIMBUS_ACTIVITY_RECORD_INPUT_SCHEMA.shape,
|
|
195
|
+
name: z.string().nullable().optional(),
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
export const JOB_NIMBUS_ACTIVITY_ACTOR_SCHEMA = z
|
|
199
|
+
.object({
|
|
200
|
+
id: JOB_NIMBUS_ID_SCHEMA,
|
|
201
|
+
name: z.string(),
|
|
202
|
+
})
|
|
203
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
204
|
+
|
|
205
|
+
export const JOB_NIMBUS_ACTIVITY_SCHEMA = z
|
|
206
|
+
.object({
|
|
207
|
+
activityAction: z.string(),
|
|
208
|
+
activityTypeId: z.number().int(),
|
|
209
|
+
content: z.string(),
|
|
210
|
+
createdAt: z
|
|
211
|
+
.union([z.date(), z.iso.datetime({ offset: true })])
|
|
212
|
+
.transform((value) => (value instanceof Date ? value : new Date(value))),
|
|
213
|
+
createdBy: JOB_NIMBUS_ACTIVITY_ACTOR_SCHEMA,
|
|
214
|
+
hasFiles: z.boolean(),
|
|
215
|
+
id: JOB_NIMBUS_ID_SCHEMA,
|
|
216
|
+
isEditable: z.boolean(),
|
|
217
|
+
isPrivate: z.boolean(),
|
|
218
|
+
primaryRecord: JOB_NIMBUS_ACTIVITY_RECORD_SCHEMA,
|
|
219
|
+
relatedRecords: JOB_NIMBUS_ACTIVITY_RECORD_SCHEMA.array(),
|
|
220
|
+
updatedAt: z
|
|
221
|
+
.union([z.date(), z.iso.datetime({ offset: true })])
|
|
222
|
+
.transform((value) => (value instanceof Date ? value : new Date(value))),
|
|
223
|
+
})
|
|
224
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
225
|
+
|
|
226
|
+
export const JOB_NIMBUS_ACTIVITY_TYPE_SCHEMA = z.object({
|
|
227
|
+
displayName: z.string(),
|
|
228
|
+
id: z.number().int(),
|
|
229
|
+
isArchived: z.boolean(),
|
|
230
|
+
isSystem: z.boolean(),
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Wraps a Platform response schema in JobNimbus's data envelope.
|
|
235
|
+
*
|
|
236
|
+
* @param dataSchema - Schema for the enveloped data value.
|
|
237
|
+
*/
|
|
238
|
+
export function jobNimbusDataSchema<TSchema extends z.ZodType>(
|
|
239
|
+
dataSchema: TSchema,
|
|
240
|
+
) {
|
|
241
|
+
return z.object({ data: dataSchema }).catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export const JOB_NIMBUS_ACTIVITY_PAGE_SCHEMA = z
|
|
245
|
+
.object({
|
|
246
|
+
data: JOB_NIMBUS_ACTIVITY_SCHEMA.array(),
|
|
247
|
+
pagination: z.object({
|
|
248
|
+
nextCursor: z.string().nullable(),
|
|
249
|
+
pageSize: z.number().int().positive(),
|
|
250
|
+
}),
|
|
251
|
+
})
|
|
252
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
253
|
+
|
|
254
|
+
export const JOB_NIMBUS_WORKFLOW_STATUS_SCHEMA = z
|
|
255
|
+
.object({
|
|
256
|
+
id: z.number().int(),
|
|
257
|
+
isActive: z.boolean(),
|
|
258
|
+
isArchived: z.boolean(),
|
|
259
|
+
isClosed: z.boolean(),
|
|
260
|
+
isLead: z.boolean(),
|
|
261
|
+
name: z.string(),
|
|
262
|
+
order: z.number().int(),
|
|
263
|
+
})
|
|
264
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
265
|
+
|
|
266
|
+
export const JOB_NIMBUS_WORKFLOW_SCHEMA = z
|
|
267
|
+
.object({
|
|
268
|
+
id: z.number().int(),
|
|
269
|
+
isActive: z.boolean(),
|
|
270
|
+
name: z.string(),
|
|
271
|
+
objectType: z.enum(["contact", "job", "workorder"]),
|
|
272
|
+
order: z.number().int(),
|
|
273
|
+
status: JOB_NIMBUS_WORKFLOW_STATUS_SCHEMA.array(),
|
|
274
|
+
})
|
|
275
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
276
|
+
|
|
277
|
+
export const JOB_NIMBUS_FILE_TYPE_SCHEMA = z.object({
|
|
278
|
+
fileTypeId: z.number().int(),
|
|
279
|
+
isActive: z.boolean(),
|
|
280
|
+
typeName: z.string(),
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
export const JOB_NIMBUS_TASK_TYPE_SCHEMA = z.object({
|
|
284
|
+
defaultName: z.string(),
|
|
285
|
+
hideFromCalendarView: z.boolean(),
|
|
286
|
+
hideFromTaskList: z.boolean(),
|
|
287
|
+
icon: z.string(),
|
|
288
|
+
isActive: z.boolean(),
|
|
289
|
+
taskTypeId: z.number().int(),
|
|
290
|
+
typeName: z.string(),
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
export const JOB_NIMBUS_LEGACY_ACTIVITY_TYPE_SCHEMA = z.object({
|
|
294
|
+
activityTypeId: z.number().int(),
|
|
295
|
+
isActive: z.boolean(),
|
|
296
|
+
showInJobShare: z.boolean(),
|
|
297
|
+
typeName: z.string(),
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
export const JOB_NIMBUS_SOURCE_SCHEMA = z.object({
|
|
301
|
+
isActive: z.boolean(),
|
|
302
|
+
jobSourceId: z.number().int(),
|
|
303
|
+
sourceName: z.string(),
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
export const JOB_NIMBUS_ACCOUNT_SETTINGS_SCHEMA = z
|
|
307
|
+
.object({
|
|
308
|
+
activityTypes: JOB_NIMBUS_LEGACY_ACTIVITY_TYPE_SCHEMA.array().optional(),
|
|
309
|
+
fileTypes: JOB_NIMBUS_FILE_TYPE_SCHEMA.array().optional(),
|
|
310
|
+
sources: JOB_NIMBUS_SOURCE_SCHEMA.array().optional(),
|
|
311
|
+
taskTypes: JOB_NIMBUS_TASK_TYPE_SCHEMA.array().optional(),
|
|
312
|
+
workflows: JOB_NIMBUS_WORKFLOW_SCHEMA.array().optional(),
|
|
313
|
+
})
|
|
314
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
315
|
+
|
|
316
|
+
export const JOB_NIMBUS_GROUP_SCHEMA = z
|
|
317
|
+
.object({
|
|
318
|
+
managers: JOB_NIMBUS_ID_SCHEMA.array(),
|
|
319
|
+
members: JOB_NIMBUS_ID_SCHEMA.array(),
|
|
320
|
+
name: z.string(),
|
|
321
|
+
})
|
|
322
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
323
|
+
|
|
324
|
+
export const JOB_NIMBUS_USER_SCHEMA = z
|
|
325
|
+
.object({
|
|
326
|
+
calendarColor: NULLABLE_STRING_SCHEMA,
|
|
327
|
+
email: z.email(),
|
|
328
|
+
firstName: z.string(),
|
|
329
|
+
id: JOB_NIMBUS_ID_SCHEMA,
|
|
330
|
+
imageUrl: NULLABLE_STRING_SCHEMA,
|
|
331
|
+
isActive: z.boolean(),
|
|
332
|
+
lastName: z.string(),
|
|
333
|
+
})
|
|
334
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
335
|
+
|
|
336
|
+
export const JOB_NIMBUS_USERS_RESPONSE_SCHEMA = z
|
|
337
|
+
.object({
|
|
338
|
+
dateUpdated: z.date().optional(),
|
|
339
|
+
users: JOB_NIMBUS_USER_SCHEMA.array(),
|
|
340
|
+
})
|
|
341
|
+
.catchall(JOB_NIMBUS_VALUE_SCHEMA)
|
|
342
|
+
|
|
343
|
+
export const JOB_NIMBUS_COMPANY_SCHEMA = z.object({
|
|
344
|
+
companyId: JOB_NIMBUS_ID_SCHEMA,
|
|
345
|
+
companyLogoUrl: z.string().nullable(),
|
|
346
|
+
companyName: z.string(),
|
|
347
|
+
isCurrent: z.boolean(),
|
|
348
|
+
isPrimary: z.boolean(),
|
|
349
|
+
lastAccessedAt: z.iso.datetime({ offset: true }).nullable(),
|
|
350
|
+
userAvatarUrl: z.string().nullable(),
|
|
351
|
+
})
|