-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathterritory.zod.ts
More file actions
88 lines (78 loc) · 2.89 KB
/
territory.zod.ts
File metadata and controls
88 lines (78 loc) · 2.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { z } from 'zod';
import { SnakeCaseIdentifierSchema } from '../shared/identifiers.zod';
/**
* Territory Management Protocol
* Defines a matrix reporting structure that exists parallel to the Role Hierarchy.
*
* USE CASE:
* - Enterprise Sales Teams (Geo-based: "EMEA", "APAC")
* - Industry Verticals (Industry-based: "Healthcare", "Financial")
* - Strategic Accounts (Account-based: "Strategic Accounts")
*
* DIFFERENCE FROM ROLE:
* - Role: Hierarchy of PEOPLE (Who reports to whom). Stable. HR-driven.
* - Territory: Hierarchy of ACCOUNTS/REVENUE (Who owns which market). Flexible. Sales-driven.
* - One User can be assigned to MANY Territories (Matrix).
* - One User has only ONE Role (Tree).
*/
export const TerritoryType = z.enum([
'geography', // Region/Country/City
'industry', // Vertical
'named_account', // Key Accounts
'product_line' // Product Specialty
]);
/**
* Territory Model Schema
* A container for a version of territory planning.
* (e.g. "Fiscal Year 2024 Planning" vs "Fiscal Year 2025 Planning")
*/
export const TerritoryModelSchema = z.object({
name: z.string().describe('Model Name (e.g. FY24 Planning)'),
state: z.enum(['planning', 'active', 'archived']).default('planning'),
startDate: z.string().optional(),
endDate: z.string().optional(),
});
/**
* Territory Node Schema
* A single node in the territory tree.
*
* **NAMING CONVENTION:**
* Territory names are machine identifiers and must be lowercase snake_case.
*
* @example Good territory names
* - 'west_coast'
* - 'emea_region'
* - 'healthcare_vertical'
* - 'strategic_accounts'
*
* @example Bad territory names (will be rejected)
* - 'WestCoast' (PascalCase)
* - 'West Coast' (spaces)
*/
export const TerritorySchema = z.object({
/** Identity */
name: SnakeCaseIdentifierSchema.describe('Territory unique name (lowercase snake_case)'),
label: z.string().describe('Territory Label (e.g. "West Coast")'),
/** Structure */
modelId: z.string().describe('Belongs to which Territory Model'),
parent: z.string().optional().describe('Parent Territory'),
type: TerritoryType.default('geography'),
/**
* Assignment Rules (The "Magic")
* How do accounts automatically fall into this territory?
* e.g. "BillingCountry = 'US' AND BillingState = 'CA'"
*/
assignmentRule: z.string().optional().describe('Criteria based assignment rule'),
/**
* User Assignment
* Users assigned to work this territory.
*/
assignedUsers: z.array(z.string()).optional(),
/** Access Level */
accountAccess: z.enum(['read', 'edit']).default('read'),
opportunityAccess: z.enum(['read', 'edit']).default('read'),
caseAccess: z.enum(['read', 'edit']).default('read'),
});
export type Territory = z.infer<typeof TerritorySchema>;
export type TerritoryModel = z.infer<typeof TerritoryModelSchema>;