repzo 1.0.288 → 1.0.290

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.
@@ -0,0 +1,349 @@
1
+ openapi: 3.0.3
2
+ info:
3
+ title: Repzo API - Geo Zone
4
+ version: 1.0.0
5
+ description: |
6
+ A **Geo Zone** is a named geographic area defined by a single GeoJSON
7
+ polygon. Zones are assigned to reps (via the rep's `assigned_geo_zones`
8
+ array — currently capped at one zone per rep) so the mobile app can
9
+ enforce location policies — e.g. whether a rep may start the day or
10
+ start a visit while outside their zone.
11
+
12
+ **Who uses it.** Admins manage zones from the back office and assign
13
+ them through the `rep` service. The mobile app reads zones (with the
14
+ incremental `from_updatedAt` cursor) to evaluate policies offline.
15
+
16
+ **Multi-tenancy & lifecycle.** Records are scoped by
17
+ `company_namespace[]` (injected from session — never sent in the body)
18
+ and use **soft-delete** via `disabled: true`. `name` is unique per
19
+ namespace among non-deleted zones. A zone that is still assigned to one
20
+ or more reps **cannot be disabled or deleted** — the request is rejected
21
+ with `400` naming the assigned reps; reassign them first.
22
+
23
+ **Validation.** A zone cannot be saved without a `name` and a valid
24
+ polygon: the polygon needs at least one closed linear ring of 4+
25
+ `[longitude, latitude]` positions (first and last must match, longitude
26
+ in ±180, latitude in ±90). Violations return a `400` naming the failing
27
+ ring.
28
+
29
+ **Key relationships.** Referenced by `rep.assigned_geo_zones`. The
30
+ `editor` audit object (who created / last updated the zone) is stamped
31
+ server-side from the session token.
32
+ servers:
33
+ - url: https://sv.api.repzo.me
34
+ security:
35
+ - ApiKeyAuth: []
36
+ - JwtAuth: []
37
+ paths:
38
+ /geo-zone:
39
+ get:
40
+ summary: Find geo zones
41
+ operationId: findGeoZones
42
+ parameters:
43
+ - in: query
44
+ name: _id
45
+ description: Filter by zone `_id`. Pass once or as `?_id[]=...` for multiple.
46
+ schema:
47
+ oneOf:
48
+ - type: string
49
+ - type: array
50
+ items: { type: string }
51
+ - in: query
52
+ name: name
53
+ description: Exact-match on zone name. Pass once or as `?name[]=...` for multiple.
54
+ schema:
55
+ oneOf:
56
+ - type: string
57
+ - type: array
58
+ items: { type: string }
59
+ - in: query
60
+ name: search
61
+ description: Case-insensitive substring search on `name`.
62
+ schema: { type: string }
63
+ example: north
64
+ - in: query
65
+ name: disabled
66
+ description: Include disabled (soft-deleted) zones. Defaults to `false`.
67
+ schema: { type: boolean, default: false }
68
+ - in: query
69
+ name: inject_assigned_reps
70
+ description: |
71
+ When truthy, each returned zone is enriched with an
72
+ `assigned_reps[]` array (`_id`, `name`) of the active reps whose
73
+ `assigned_geo_zones` contains it.
74
+ schema: { type: boolean, default: false }
75
+ - in: query
76
+ name: from_updatedAt
77
+ description: Cursor — zones updated on or after this Unix timestamp (ms). Used by the mobile app for incremental sync.
78
+ schema: { type: number }
79
+ - in: query
80
+ name: to_updatedAt
81
+ description: Cursor — zones updated on or before this Unix timestamp (ms).
82
+ schema: { type: number }
83
+ - in: query
84
+ name: from_createdAt
85
+ description: Cursor — zones created on or after this Unix timestamp (ms).
86
+ schema: { type: number }
87
+ - in: query
88
+ name: to_createdAt
89
+ description: Cursor — zones created on or before this Unix timestamp (ms).
90
+ schema: { type: number }
91
+ - in: query
92
+ name: per_page
93
+ description: Page size. Defaults to the server's configured pagination limit.
94
+ schema: { type: integer, minimum: 1, maximum: 500 }
95
+ example: 50
96
+ - in: query
97
+ name: page
98
+ description: 1-indexed page number.
99
+ schema: { type: integer, minimum: 1 }
100
+ example: 1
101
+ responses:
102
+ "200":
103
+ description: A paginated list of geo zones matching the filter.
104
+ content:
105
+ application/json:
106
+ schema:
107
+ $ref: "#/components/schemas/GeoZoneFindResult"
108
+ post:
109
+ summary: Create a geo zone
110
+ operationId: createGeoZone
111
+ requestBody:
112
+ required: true
113
+ content:
114
+ application/json:
115
+ schema:
116
+ $ref: "#/components/schemas/GeoZoneCreateBody"
117
+ responses:
118
+ "201":
119
+ description: The newly-created geo zone. `editor` is stamped from the session token.
120
+ content:
121
+ application/json:
122
+ schema:
123
+ $ref: "#/components/schemas/GeoZoneSchema"
124
+ "400":
125
+ description: |
126
+ Validation failure — missing `name`, missing polygon, or an
127
+ invalid polygon (open ring, fewer than 4 positions, out-of-range
128
+ coordinates). The message names the failing ring. Also returned
129
+ when the zone name already exists in the namespace.
130
+ /geo-zone/{id}:
131
+ get:
132
+ summary: Get a geo zone by ID
133
+ operationId: getGeoZone
134
+ parameters:
135
+ - in: path
136
+ name: id
137
+ required: true
138
+ schema: { type: string }
139
+ - in: query
140
+ name: inject_assigned_reps
141
+ description: When truthy, the zone is enriched with an `assigned_reps[]` array (`_id`, `name`).
142
+ schema: { type: boolean, default: false }
143
+ responses:
144
+ "200":
145
+ description: The geo zone document for the given `_id`.
146
+ content:
147
+ application/json:
148
+ schema:
149
+ $ref: "#/components/schemas/GeoZoneSchema"
150
+ put:
151
+ summary: Update a geo zone
152
+ operationId: updateGeoZone
153
+ description: |
154
+ Partial update — only the fields present in the body are changed.
155
+ `polygon`, when sent, replaces the zone's polygon and is validated
156
+ like create. Setting `disabled: true` deactivates the zone and is
157
+ **rejected with `400` while any rep is still assigned to it**;
158
+ reassign those reps first.
159
+ parameters:
160
+ - in: path
161
+ name: id
162
+ required: true
163
+ schema: { type: string }
164
+ requestBody:
165
+ required: true
166
+ content:
167
+ application/json:
168
+ schema:
169
+ $ref: "#/components/schemas/GeoZoneUpdateBody"
170
+ responses:
171
+ "200":
172
+ description: The geo zone document after the update is applied.
173
+ content:
174
+ application/json:
175
+ schema:
176
+ $ref: "#/components/schemas/GeoZoneSchema"
177
+ "400":
178
+ description: Zone not found, polygon validation failure, or the zone is being disabled while reps are still assigned to it.
179
+ delete:
180
+ summary: Remove a geo zone (soft-delete)
181
+ operationId: removeGeoZone
182
+ description: |
183
+ Soft-deletes the zone (`disabled: true`). Rejected with `400` while
184
+ any rep is still assigned to it — the error names the assigned reps;
185
+ reassign them to another zone first.
186
+ parameters:
187
+ - in: path
188
+ name: id
189
+ required: true
190
+ schema: { type: string }
191
+ responses:
192
+ "200":
193
+ description: "The geo zone document after soft-deletion (`disabled: true`)."
194
+ content:
195
+ application/json:
196
+ schema:
197
+ $ref: "#/components/schemas/GeoZoneSchema"
198
+ "400":
199
+ description: Zone not found, or the zone is still assigned to one or more reps.
200
+ components:
201
+ securitySchemes:
202
+ ApiKeyAuth:
203
+ type: apiKey
204
+ in: header
205
+ name: api-key
206
+ description: |
207
+ Server-issued API key. Also accepted via the `x-api-key` header or the
208
+ `?apiKey=` query parameter as fallbacks.
209
+ JwtAuth:
210
+ type: apiKey
211
+ in: header
212
+ name: Authorization
213
+ description: |
214
+ Raw JWT in the `Authorization` header — **no `Bearer ` prefix**. Obtained from
215
+ `POST /authenticate` (admin / rep / client login).
216
+ schemas:
217
+ GeoZoneFindResult:
218
+ type: object
219
+ description: Paginated list of geo zones.
220
+ properties:
221
+ data:
222
+ type: array
223
+ items:
224
+ $ref: "#/components/schemas/GeoZoneSchema"
225
+ total_result: { type: number }
226
+ current_count: { type: number }
227
+ total_pages: { type: number }
228
+ current_page: { type: number }
229
+ per_page: { type: number }
230
+ first_page_url: { type: string }
231
+ last_page_url: { type: string }
232
+ next_page_url: { type: string, nullable: true }
233
+ prev_page_url: { type: string, nullable: true }
234
+ path: { type: string }
235
+ GeoZoneSchema:
236
+ type: object
237
+ description: Geo zone document as stored.
238
+ properties:
239
+ _id: { type: string }
240
+ name:
241
+ {
242
+ type: string,
243
+ description: Unique per namespace among non-deleted zones.,
244
+ }
245
+ description: { type: string }
246
+ polygon:
247
+ $ref: "#/components/schemas/GeoZonePolygon"
248
+ disabled:
249
+ { type: boolean, description: Soft-delete / deactivation flag. }
250
+ editor:
251
+ $ref: "#/components/schemas/GeoZoneEditor"
252
+ assigned_reps:
253
+ type: array
254
+ description: "Present only when the request sent `inject_assigned_reps=true` — the active reps assigned to this zone."
255
+ items:
256
+ type: object
257
+ properties:
258
+ _id: { type: string }
259
+ name: { type: string }
260
+ company_namespace:
261
+ type: array
262
+ items: { type: string }
263
+ description: Tenant key set on save from session.
264
+ createdAt: { type: string, format: date-time }
265
+ updatedAt: { type: string, format: date-time }
266
+ __v: { type: number }
267
+ GeoZonePolygon:
268
+ type: object
269
+ description: |
270
+ A GeoJSON Polygon. `coordinates` is an array of linear rings; the
271
+ first ring is the outer boundary, any further rings are holes.
272
+ required: [coordinates]
273
+ properties:
274
+ type:
275
+ type: string
276
+ enum: [Polygon]
277
+ default: Polygon
278
+ coordinates:
279
+ type: array
280
+ minItems: 1
281
+ description: |
282
+ Array of linear rings. Each ring is an array of at least 4
283
+ positions and must be closed (first position equals the last).
284
+ Positions are ordered as **longitude, latitude** (longitude
285
+ first) — longitude in [-180, 180], latitude in [-90, 90].
286
+ items:
287
+ type: array
288
+ minItems: 4
289
+ description: "A closed linear ring of `[longitude, latitude]` positions."
290
+ items:
291
+ type: array
292
+ minItems: 2
293
+ maxItems: 2
294
+ items: { type: number }
295
+ example:
296
+ - - [35.910, 31.954]
297
+ - [35.930, 31.954]
298
+ - [35.930, 31.970]
299
+ - [35.910, 31.954]
300
+ GeoZoneEditor:
301
+ type: object
302
+ description: Audit stamp of who created / last updated the zone. Set server-side from the session token — never sent by the client.
303
+ properties:
304
+ _id: { type: string }
305
+ type: { type: string, enum: [admin, rep, tenant, client] }
306
+ name: { type: string }
307
+ rep: { type: string }
308
+ admin: { type: string }
309
+ client: { type: string }
310
+ tenant: { type: string }
311
+ GeoZoneCreateBody:
312
+ type: object
313
+ description: |
314
+ Body for creating a geo zone. The `editor` audit stamp is stamped
315
+ server-side from the session token (not accepted on create). The tenant
316
+ key (`company_namespace`) is normally injected from session and can be
317
+ omitted — it is optional here for integration / cross-namespace callers.
318
+ The soft-delete flag (`disabled`) defaults to `false`.
319
+ required:
320
+ - name
321
+ - polygon
322
+ properties:
323
+ name:
324
+ type: string
325
+ description: Zone name — required and unique per namespace among non-deleted zones.
326
+ description: { type: string }
327
+ polygon:
328
+ $ref: "#/components/schemas/GeoZonePolygon"
329
+ company_namespace:
330
+ type: array
331
+ items: { type: string }
332
+ description: Tenant key — normally injected from session; optional, for cross-namespace callers.
333
+ GeoZoneUpdateBody:
334
+ type: object
335
+ description: |
336
+ Body for updating a geo zone — only the fields present are changed.
337
+ The tenant key (`company_namespace`) is derived from session. The
338
+ `editor` audit stamp is normally refreshed server-side but may be
339
+ provided (optional).
340
+ properties:
341
+ name: { type: string }
342
+ description: { type: string }
343
+ polygon:
344
+ $ref: "#/components/schemas/GeoZonePolygon"
345
+ disabled:
346
+ type: boolean
347
+ description: "Soft-delete / deactivation flag. Setting `true` is rejected while any rep is still assigned to this zone."
348
+ editor:
349
+ $ref: "#/components/schemas/GeoZoneEditor"
package/src/oas/rep.yaml CHANGED
@@ -106,6 +106,13 @@ paths:
106
106
  items:
107
107
  type: string
108
108
  - type: string
109
+ assigned_geo_zones:
110
+ description: Filter by assigned geo-zone `_id`s.
111
+ oneOf:
112
+ - type: array
113
+ items:
114
+ type: string
115
+ - type: string
109
116
  lines:
110
117
  oneOf:
111
118
  - type: array
@@ -164,6 +171,7 @@ paths:
164
171
  "cover_photo",
165
172
  "assigned_plan",
166
173
  "assigned_retail_execution_templates",
174
+ "assigned_geo_zones",
167
175
  "warehouse",
168
176
  ]
169
177
  withProductLines:
@@ -285,6 +293,10 @@ paths:
285
293
  type: boolean
286
294
  "permissions.rep_can_create_skip_geofence_at_visit_end_approval_request":
287
295
  type: boolean
296
+ "permissions.rep_can_start_visit_out_of_geofence":
297
+ type: boolean
298
+ "permissions.rep_can_create_approval_request_to_start_visit_out_of_geofence":
299
+ type: boolean
288
300
  "permissions.rep_can_create_credit_invoice_for_cash_client":
289
301
  type: boolean
290
302
  "permissions.rep_can_view_client_credit_limit":
@@ -672,6 +684,12 @@ components:
672
684
  rep_can_create_skip_geofence_at_visit_end_approval_request:
673
685
  type: boolean
674
686
  description: Can rep create skip geofence at visit end approval request
687
+ rep_can_start_visit_out_of_geofence:
688
+ type: boolean
689
+ description: Can rep start a visit while outside their geofence / geo zone
690
+ rep_can_create_approval_request_to_start_visit_out_of_geofence:
691
+ type: boolean
692
+ description: Can rep create an approval request to start a visit while out of geofence
675
693
  rep_can_create_credit_invoice_for_cash_client:
676
694
  type: boolean
677
695
  description: Can rep create credit invoice for cash client
@@ -785,6 +803,11 @@ components:
785
803
  items:
786
804
  type: string
787
805
  description: Assigned targets
806
+ assigned_geo_zones:
807
+ type: array
808
+ items:
809
+ type: string
810
+ description: "`_id`s of the geo zones assigned to this rep (ref `geo-zone`) — currently at most one."
788
811
  lines:
789
812
  type: array
790
813
  items:
@@ -1122,6 +1145,12 @@ components:
1122
1145
  rep_can_create_skip_geofence_at_visit_end_approval_request:
1123
1146
  type: boolean
1124
1147
  description: Can rep create skip geofence at visit end approval request
1148
+ rep_can_start_visit_out_of_geofence:
1149
+ type: boolean
1150
+ description: Can rep start a visit while outside their geofence / geo zone
1151
+ rep_can_create_approval_request_to_start_visit_out_of_geofence:
1152
+ type: boolean
1153
+ description: Can rep create an approval request to start a visit while out of geofence
1125
1154
  rep_can_create_credit_invoice_for_cash_client:
1126
1155
  type: boolean
1127
1156
  description: Can rep create credit invoice for cash client
@@ -1235,6 +1264,11 @@ components:
1235
1264
  items:
1236
1265
  type: string
1237
1266
  description: Assigned targets
1267
+ assigned_geo_zones:
1268
+ type: array
1269
+ items:
1270
+ type: string
1271
+ description: "`_id`s of the geo zones assigned to this rep (ref `geo-zone`) — currently at most one."
1238
1272
  lines:
1239
1273
  type: array
1240
1274
  items:
@@ -1562,6 +1596,12 @@ components:
1562
1596
  rep_can_create_skip_geofence_at_visit_end_approval_request:
1563
1597
  type: boolean
1564
1598
  description: Can rep create skip geofence at visit end approval request
1599
+ rep_can_start_visit_out_of_geofence:
1600
+ type: boolean
1601
+ description: Can rep start a visit while outside their geofence / geo zone
1602
+ rep_can_create_approval_request_to_start_visit_out_of_geofence:
1603
+ type: boolean
1604
+ description: Can rep create an approval request to start a visit while out of geofence
1565
1605
  rep_can_create_credit_invoice_for_cash_client:
1566
1606
  type: boolean
1567
1607
  description: Can rep create credit invoice for cash client
@@ -1675,6 +1715,11 @@ components:
1675
1715
  items:
1676
1716
  type: string
1677
1717
  description: Assigned targets
1718
+ assigned_geo_zones:
1719
+ type: array
1720
+ items:
1721
+ type: string
1722
+ description: "`_id`s of the geo zones assigned to this rep (ref `geo-zone`) — currently at most one."
1678
1723
  lines:
1679
1724
  type: array
1680
1725
  items:
@@ -93,6 +93,36 @@ components:
93
93
  type: string
94
94
  description:
95
95
  type: string
96
+ start_day_outside_active_zone_action:
97
+ type: string
98
+ enum: [monitor, warning, block]
99
+ default: warning
100
+ description: "Action when a rep starts their day outside their active geo zone: `monitor` (allow and record silently), `warning` (allow but warn), or `block` (prevent)."
101
+ start_visit_outside_active_zone_action:
102
+ type: string
103
+ enum: [monitor, warning, block]
104
+ default: warning
105
+ description: "Action when a rep starts a visit outside their active geo zone (same `monitor`/`warning`/`block` semantics as `start_day_outside_active_zone_action`)."
106
+ work_outside_active_zone_action:
107
+ type: string
108
+ enum: [monitor, warning, block]
109
+ default: warning
110
+ description: "Action when a rep performs work outside their active geo zone (same `monitor`/`warning`/`block` semantics as `start_day_outside_active_zone_action`)."
111
+ no_active_zone_action:
112
+ type: string
113
+ enum: [allow, block]
114
+ default: allow
115
+ description: "Behavior when a rep has no active geo zone: `allow` (work unrestricted) or `block` (prevent working)."
116
+ use_accuracy_radius_near_zone_boundary:
117
+ type: boolean
118
+ default: true
119
+ description: "When true, the device GPS accuracy radius is considered near a zone boundary — a position whose accuracy circle overlaps the zone counts as inside it."
120
+ zone_boundary_grace_minutes:
121
+ type: integer
122
+ minimum: 0
123
+ maximum: 60
124
+ default: 5
125
+ description: "Grace period in minutes around zone-boundary transitions before the configured outside-zone action is enforced (integer 0-60)."
96
126
  SettingsUpdateResult:
97
127
  type: object
98
128
  properties:
@@ -115,6 +145,36 @@ components:
115
145
  type: string
116
146
  description:
117
147
  type: string
148
+ start_day_outside_active_zone_action:
149
+ type: string
150
+ enum: [monitor, warning, block]
151
+ default: warning
152
+ description: "Action when a rep starts their day outside their active geo zone: `monitor` (allow and record silently), `warning` (allow but warn), or `block` (prevent)."
153
+ start_visit_outside_active_zone_action:
154
+ type: string
155
+ enum: [monitor, warning, block]
156
+ default: warning
157
+ description: "Action when a rep starts a visit outside their active geo zone (same `monitor`/`warning`/`block` semantics as `start_day_outside_active_zone_action`)."
158
+ work_outside_active_zone_action:
159
+ type: string
160
+ enum: [monitor, warning, block]
161
+ default: warning
162
+ description: "Action when a rep performs work outside their active geo zone (same `monitor`/`warning`/`block` semantics as `start_day_outside_active_zone_action`)."
163
+ no_active_zone_action:
164
+ type: string
165
+ enum: [allow, block]
166
+ default: allow
167
+ description: "Behavior when a rep has no active geo zone: `allow` (work unrestricted) or `block` (prevent working)."
168
+ use_accuracy_radius_near_zone_boundary:
169
+ type: boolean
170
+ default: true
171
+ description: "When true, the device GPS accuracy radius is considered near a zone boundary — a position whose accuracy circle overlaps the zone counts as inside it."
172
+ zone_boundary_grace_minutes:
173
+ type: integer
174
+ minimum: 0
175
+ maximum: 60
176
+ default: 5
177
+ description: "Grace period in minutes around zone-boundary transitions before the configured outside-zone action is enforced (integer 0-60)."
118
178
  createdAt:
119
179
  type: string
120
180
  format: date-time