affinity-sdk 0.9.5__py3-none-any.whl
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.
- affinity/__init__.py +139 -0
- affinity/cli/__init__.py +7 -0
- affinity/cli/click_compat.py +27 -0
- affinity/cli/commands/__init__.py +1 -0
- affinity/cli/commands/_entity_files_dump.py +219 -0
- affinity/cli/commands/_list_entry_fields.py +41 -0
- affinity/cli/commands/_v1_parsing.py +77 -0
- affinity/cli/commands/company_cmds.py +2139 -0
- affinity/cli/commands/completion_cmd.py +33 -0
- affinity/cli/commands/config_cmds.py +540 -0
- affinity/cli/commands/entry_cmds.py +33 -0
- affinity/cli/commands/field_cmds.py +413 -0
- affinity/cli/commands/interaction_cmds.py +875 -0
- affinity/cli/commands/list_cmds.py +3152 -0
- affinity/cli/commands/note_cmds.py +433 -0
- affinity/cli/commands/opportunity_cmds.py +1174 -0
- affinity/cli/commands/person_cmds.py +1980 -0
- affinity/cli/commands/query_cmd.py +444 -0
- affinity/cli/commands/relationship_strength_cmds.py +62 -0
- affinity/cli/commands/reminder_cmds.py +595 -0
- affinity/cli/commands/resolve_url_cmd.py +127 -0
- affinity/cli/commands/session_cmds.py +84 -0
- affinity/cli/commands/task_cmds.py +110 -0
- affinity/cli/commands/version_cmd.py +29 -0
- affinity/cli/commands/whoami_cmd.py +36 -0
- affinity/cli/config.py +108 -0
- affinity/cli/context.py +749 -0
- affinity/cli/csv_utils.py +195 -0
- affinity/cli/date_utils.py +42 -0
- affinity/cli/decorators.py +77 -0
- affinity/cli/errors.py +28 -0
- affinity/cli/field_utils.py +355 -0
- affinity/cli/formatters.py +551 -0
- affinity/cli/help_json.py +283 -0
- affinity/cli/logging.py +100 -0
- affinity/cli/main.py +261 -0
- affinity/cli/options.py +53 -0
- affinity/cli/paths.py +32 -0
- affinity/cli/progress.py +183 -0
- affinity/cli/query/__init__.py +163 -0
- affinity/cli/query/aggregates.py +357 -0
- affinity/cli/query/dates.py +194 -0
- affinity/cli/query/exceptions.py +147 -0
- affinity/cli/query/executor.py +1236 -0
- affinity/cli/query/filters.py +248 -0
- affinity/cli/query/models.py +333 -0
- affinity/cli/query/output.py +331 -0
- affinity/cli/query/parser.py +619 -0
- affinity/cli/query/planner.py +430 -0
- affinity/cli/query/progress.py +270 -0
- affinity/cli/query/schema.py +439 -0
- affinity/cli/render.py +1589 -0
- affinity/cli/resolve.py +222 -0
- affinity/cli/resolvers.py +249 -0
- affinity/cli/results.py +308 -0
- affinity/cli/runner.py +218 -0
- affinity/cli/serialization.py +65 -0
- affinity/cli/session_cache.py +276 -0
- affinity/cli/types.py +70 -0
- affinity/client.py +771 -0
- affinity/clients/__init__.py +19 -0
- affinity/clients/http.py +3664 -0
- affinity/clients/pipeline.py +165 -0
- affinity/compare.py +501 -0
- affinity/downloads.py +114 -0
- affinity/exceptions.py +615 -0
- affinity/filters.py +1128 -0
- affinity/hooks.py +198 -0
- affinity/inbound_webhooks.py +302 -0
- affinity/models/__init__.py +163 -0
- affinity/models/entities.py +798 -0
- affinity/models/pagination.py +513 -0
- affinity/models/rate_limit_snapshot.py +48 -0
- affinity/models/secondary.py +413 -0
- affinity/models/types.py +663 -0
- affinity/policies.py +40 -0
- affinity/progress.py +22 -0
- affinity/py.typed +0 -0
- affinity/services/__init__.py +42 -0
- affinity/services/companies.py +1286 -0
- affinity/services/lists.py +1892 -0
- affinity/services/opportunities.py +1330 -0
- affinity/services/persons.py +1348 -0
- affinity/services/rate_limits.py +173 -0
- affinity/services/tasks.py +193 -0
- affinity/services/v1_only.py +2445 -0
- affinity/types.py +83 -0
- affinity_sdk-0.9.5.dist-info/METADATA +622 -0
- affinity_sdk-0.9.5.dist-info/RECORD +92 -0
- affinity_sdk-0.9.5.dist-info/WHEEL +4 -0
- affinity_sdk-0.9.5.dist-info/entry_points.txt +2 -0
- affinity_sdk-0.9.5.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from contextlib import ExitStack
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.progress import BarColumn, Progress, TaskID, TextColumn, TimeElapsedColumn
|
|
9
|
+
|
|
10
|
+
from affinity.models.secondary import Reminder, ReminderCreate, ReminderUpdate
|
|
11
|
+
from affinity.models.types import ReminderResetType, ReminderStatus, ReminderType
|
|
12
|
+
from affinity.types import CompanyId, OpportunityId, PersonId, ReminderIdType, UserId
|
|
13
|
+
|
|
14
|
+
from ..click_compat import RichCommand, RichGroup, click
|
|
15
|
+
from ..context import CLIContext
|
|
16
|
+
from ..decorators import category, destructive
|
|
17
|
+
from ..errors import CLIError
|
|
18
|
+
from ..options import output_options
|
|
19
|
+
from ..results import CommandContext
|
|
20
|
+
from ..runner import CommandOutput, run_command
|
|
21
|
+
from ._v1_parsing import parse_choice, parse_iso_datetime
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group(name="reminder", cls=RichGroup)
|
|
25
|
+
def reminder_group() -> None:
|
|
26
|
+
"""Reminder commands."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_REMINDER_TYPE_MAP = {
|
|
30
|
+
"one-time": ReminderType.ONE_TIME,
|
|
31
|
+
"recurring": ReminderType.RECURRING,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_REMINDER_RESET_MAP = {
|
|
35
|
+
"interaction": ReminderResetType.INTERACTION,
|
|
36
|
+
"email": ReminderResetType.EMAIL,
|
|
37
|
+
"meeting": ReminderResetType.MEETING,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_REMINDER_STATUS_MAP = {
|
|
41
|
+
"active": ReminderStatus.ACTIVE,
|
|
42
|
+
"completed": ReminderStatus.COMPLETED,
|
|
43
|
+
"overdue": ReminderStatus.OVERDUE,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _extract_id(value: Any) -> int | None:
|
|
48
|
+
if value is None:
|
|
49
|
+
return None
|
|
50
|
+
if hasattr(value, "id"):
|
|
51
|
+
try:
|
|
52
|
+
return int(value.id)
|
|
53
|
+
except Exception:
|
|
54
|
+
return None
|
|
55
|
+
if isinstance(value, dict):
|
|
56
|
+
for key in (
|
|
57
|
+
"id",
|
|
58
|
+
"personId",
|
|
59
|
+
"organizationId",
|
|
60
|
+
"companyId",
|
|
61
|
+
"opportunityId",
|
|
62
|
+
"person_id",
|
|
63
|
+
"organization_id",
|
|
64
|
+
"company_id",
|
|
65
|
+
"opportunity_id",
|
|
66
|
+
):
|
|
67
|
+
raw = value.get(key)
|
|
68
|
+
if raw is None:
|
|
69
|
+
continue
|
|
70
|
+
if isinstance(raw, bool):
|
|
71
|
+
continue
|
|
72
|
+
if isinstance(raw, (int, float)):
|
|
73
|
+
return int(raw)
|
|
74
|
+
if isinstance(raw, str) and raw.isdigit():
|
|
75
|
+
return int(raw)
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _reminder_payload(reminder: Reminder) -> dict[str, object]:
|
|
80
|
+
# Convert enum values back to names for CLI display
|
|
81
|
+
type_name = ReminderType(reminder.type).name.lower().replace("_", "-")
|
|
82
|
+
status_name = ReminderStatus(reminder.status).name.lower()
|
|
83
|
+
reset_type_name = (
|
|
84
|
+
ReminderResetType(reminder.reset_type).name.lower()
|
|
85
|
+
if reminder.reset_type is not None
|
|
86
|
+
else None
|
|
87
|
+
)
|
|
88
|
+
return {
|
|
89
|
+
"id": int(reminder.id),
|
|
90
|
+
"type": type_name,
|
|
91
|
+
"status": status_name,
|
|
92
|
+
"content": reminder.content,
|
|
93
|
+
"dueDate": reminder.due_date,
|
|
94
|
+
"resetType": reset_type_name,
|
|
95
|
+
"reminderDays": reminder.reminder_days,
|
|
96
|
+
"ownerId": _extract_id(reminder.owner),
|
|
97
|
+
"creatorId": _extract_id(reminder.creator),
|
|
98
|
+
"completerId": _extract_id(reminder.completer),
|
|
99
|
+
"personId": _extract_id(reminder.person),
|
|
100
|
+
"companyId": _extract_id(reminder.company),
|
|
101
|
+
"opportunityId": _extract_id(reminder.opportunity),
|
|
102
|
+
"createdAt": reminder.created_at,
|
|
103
|
+
"completedAt": reminder.completed_at,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _validate_single_entity(
|
|
108
|
+
person_id: int | None,
|
|
109
|
+
company_id: int | None,
|
|
110
|
+
opportunity_id: int | None,
|
|
111
|
+
) -> None:
|
|
112
|
+
count = sum(1 for value in (person_id, company_id, opportunity_id) if value is not None)
|
|
113
|
+
if count > 1:
|
|
114
|
+
raise CLIError(
|
|
115
|
+
"Reminders can be associated with only one entity.",
|
|
116
|
+
error_type="usage_error",
|
|
117
|
+
exit_code=2,
|
|
118
|
+
hint="Provide only one of --person-id, --company-id, or --opportunity-id.",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@category("read")
|
|
123
|
+
@reminder_group.command(name="ls", cls=RichCommand)
|
|
124
|
+
@click.option("--person-id", type=int, default=None, help="Filter by person id.")
|
|
125
|
+
@click.option("--company-id", type=int, default=None, help="Filter by company id.")
|
|
126
|
+
@click.option("--opportunity-id", type=int, default=None, help="Filter by opportunity id.")
|
|
127
|
+
@click.option("--creator-id", type=int, default=None, help="Filter by creator id.")
|
|
128
|
+
@click.option("--owner-id", type=int, default=None, help="Filter by owner id.")
|
|
129
|
+
@click.option("--completer-id", type=int, default=None, help="Filter by completer id.")
|
|
130
|
+
@click.option(
|
|
131
|
+
"--type",
|
|
132
|
+
"reminder_type",
|
|
133
|
+
type=click.Choice(sorted(_REMINDER_TYPE_MAP.keys())),
|
|
134
|
+
default=None,
|
|
135
|
+
help="Reminder type (one-time, recurring).",
|
|
136
|
+
)
|
|
137
|
+
@click.option(
|
|
138
|
+
"--reset-type",
|
|
139
|
+
type=click.Choice(sorted(_REMINDER_RESET_MAP.keys())),
|
|
140
|
+
default=None,
|
|
141
|
+
help="Reset type for recurring reminders.",
|
|
142
|
+
)
|
|
143
|
+
@click.option(
|
|
144
|
+
"--status",
|
|
145
|
+
type=click.Choice(sorted(_REMINDER_STATUS_MAP.keys())),
|
|
146
|
+
default=None,
|
|
147
|
+
help="Reminder status (active, completed, overdue).",
|
|
148
|
+
)
|
|
149
|
+
@click.option(
|
|
150
|
+
"--due-after", type=str, default=None, help="Filter reminders due after this date (ISO-8601)."
|
|
151
|
+
)
|
|
152
|
+
@click.option(
|
|
153
|
+
"--due-before", type=str, default=None, help="Filter reminders due before this date (ISO-8601)."
|
|
154
|
+
)
|
|
155
|
+
@click.option("--page-size", "-s", type=int, default=None, help="Page size (max 500).")
|
|
156
|
+
@click.option(
|
|
157
|
+
"--cursor", type=str, default=None, help="Resume from cursor (incompatible with --page-size)."
|
|
158
|
+
)
|
|
159
|
+
@click.option(
|
|
160
|
+
"--max-results", "--limit", "-n", type=int, default=None, help="Stop after N results total."
|
|
161
|
+
)
|
|
162
|
+
@click.option("--all", "-A", "all_pages", is_flag=True, help="Fetch all pages.")
|
|
163
|
+
@output_options
|
|
164
|
+
@click.pass_obj
|
|
165
|
+
def reminder_ls(
|
|
166
|
+
ctx: CLIContext,
|
|
167
|
+
*,
|
|
168
|
+
person_id: int | None,
|
|
169
|
+
company_id: int | None,
|
|
170
|
+
opportunity_id: int | None,
|
|
171
|
+
creator_id: int | None,
|
|
172
|
+
owner_id: int | None,
|
|
173
|
+
completer_id: int | None,
|
|
174
|
+
reminder_type: str | None,
|
|
175
|
+
reset_type: str | None,
|
|
176
|
+
status: str | None,
|
|
177
|
+
due_after: str | None,
|
|
178
|
+
due_before: str | None,
|
|
179
|
+
page_size: int | None,
|
|
180
|
+
cursor: str | None,
|
|
181
|
+
max_results: int | None,
|
|
182
|
+
all_pages: bool,
|
|
183
|
+
) -> None:
|
|
184
|
+
"""List reminders.
|
|
185
|
+
|
|
186
|
+
Filter by entity (--person-id, --company-id, --opportunity-id), user (--owner-id,
|
|
187
|
+
--creator-id, --completer-id), type, status, or due date range (--due-after/--due-before).
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
|
|
191
|
+
- `xaffinity reminder ls --person-id 123`
|
|
192
|
+
|
|
193
|
+
- `xaffinity reminder ls --status active --due-after 2024-01-01`
|
|
194
|
+
|
|
195
|
+
- `xaffinity reminder ls --owner-id 456 --type recurring`
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
|
|
199
|
+
client = ctx.get_client(warnings=warnings)
|
|
200
|
+
results: list[dict[str, object]] = []
|
|
201
|
+
first_page = True
|
|
202
|
+
page_token = cursor
|
|
203
|
+
|
|
204
|
+
# Build CommandContext upfront for all return paths
|
|
205
|
+
ctx_modifiers: dict[str, object] = {}
|
|
206
|
+
if person_id is not None:
|
|
207
|
+
ctx_modifiers["personId"] = person_id
|
|
208
|
+
if company_id is not None:
|
|
209
|
+
ctx_modifiers["companyId"] = company_id
|
|
210
|
+
if opportunity_id is not None:
|
|
211
|
+
ctx_modifiers["opportunityId"] = opportunity_id
|
|
212
|
+
if creator_id is not None:
|
|
213
|
+
ctx_modifiers["creatorId"] = creator_id
|
|
214
|
+
if owner_id is not None:
|
|
215
|
+
ctx_modifiers["ownerId"] = owner_id
|
|
216
|
+
if completer_id is not None:
|
|
217
|
+
ctx_modifiers["completerId"] = completer_id
|
|
218
|
+
if reminder_type:
|
|
219
|
+
ctx_modifiers["type"] = reminder_type
|
|
220
|
+
if reset_type:
|
|
221
|
+
ctx_modifiers["resetType"] = reset_type
|
|
222
|
+
if status:
|
|
223
|
+
ctx_modifiers["status"] = status
|
|
224
|
+
if due_after:
|
|
225
|
+
ctx_modifiers["dueAfter"] = due_after
|
|
226
|
+
if due_before:
|
|
227
|
+
ctx_modifiers["dueBefore"] = due_before
|
|
228
|
+
if page_size is not None:
|
|
229
|
+
ctx_modifiers["pageSize"] = page_size
|
|
230
|
+
if cursor is not None:
|
|
231
|
+
ctx_modifiers["cursor"] = cursor
|
|
232
|
+
if max_results is not None:
|
|
233
|
+
ctx_modifiers["maxResults"] = max_results
|
|
234
|
+
if all_pages:
|
|
235
|
+
ctx_modifiers["allPages"] = True
|
|
236
|
+
|
|
237
|
+
cmd_context = CommandContext(
|
|
238
|
+
name="reminder ls",
|
|
239
|
+
inputs={},
|
|
240
|
+
modifiers=ctx_modifiers,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
parsed_type = parse_choice(reminder_type, _REMINDER_TYPE_MAP, label="reminder type")
|
|
244
|
+
parsed_reset = parse_choice(reset_type, _REMINDER_RESET_MAP, label="reset type")
|
|
245
|
+
parsed_status = parse_choice(status, _REMINDER_STATUS_MAP, label="status")
|
|
246
|
+
due_before_value = (
|
|
247
|
+
parse_iso_datetime(due_before, label="due-before") if due_before else None
|
|
248
|
+
)
|
|
249
|
+
due_after_value = parse_iso_datetime(due_after, label="due-after") if due_after else None
|
|
250
|
+
person_id_value = PersonId(person_id) if person_id is not None else None
|
|
251
|
+
company_id_value = CompanyId(company_id) if company_id is not None else None
|
|
252
|
+
opportunity_id_value = OpportunityId(opportunity_id) if opportunity_id is not None else None
|
|
253
|
+
creator_id_value = UserId(creator_id) if creator_id is not None else None
|
|
254
|
+
owner_id_value = UserId(owner_id) if owner_id is not None else None
|
|
255
|
+
completer_id_value = UserId(completer_id) if completer_id is not None else None
|
|
256
|
+
|
|
257
|
+
show_progress = (
|
|
258
|
+
ctx.progress != "never"
|
|
259
|
+
and not ctx.quiet
|
|
260
|
+
and (ctx.progress == "always" or sys.stderr.isatty())
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
with ExitStack() as stack:
|
|
264
|
+
progress: Progress | None = None
|
|
265
|
+
task_id: TaskID | None = None
|
|
266
|
+
if show_progress:
|
|
267
|
+
progress = stack.enter_context(
|
|
268
|
+
Progress(
|
|
269
|
+
TextColumn("{task.description}"),
|
|
270
|
+
BarColumn(),
|
|
271
|
+
TextColumn("{task.completed} rows"),
|
|
272
|
+
TimeElapsedColumn(),
|
|
273
|
+
console=Console(file=sys.stderr),
|
|
274
|
+
transient=True,
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
task_id = progress.add_task("Fetching", total=max_results)
|
|
278
|
+
|
|
279
|
+
while True:
|
|
280
|
+
page = client.reminders.list(
|
|
281
|
+
person_id=person_id_value,
|
|
282
|
+
company_id=company_id_value,
|
|
283
|
+
opportunity_id=opportunity_id_value,
|
|
284
|
+
creator_id=creator_id_value,
|
|
285
|
+
owner_id=owner_id_value,
|
|
286
|
+
completer_id=completer_id_value,
|
|
287
|
+
type=parsed_type,
|
|
288
|
+
reset_type=parsed_reset,
|
|
289
|
+
status=parsed_status,
|
|
290
|
+
due_before=due_before_value,
|
|
291
|
+
due_after=due_after_value,
|
|
292
|
+
page_size=page_size,
|
|
293
|
+
page_token=page_token,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
for idx, reminder in enumerate(page.data):
|
|
297
|
+
results.append(_reminder_payload(reminder))
|
|
298
|
+
if progress and task_id is not None:
|
|
299
|
+
progress.update(task_id, completed=len(results))
|
|
300
|
+
if max_results is not None and len(results) >= max_results:
|
|
301
|
+
stopped_mid_page = idx < (len(page.data) - 1)
|
|
302
|
+
if stopped_mid_page:
|
|
303
|
+
warnings.append(
|
|
304
|
+
"Results limited by --max-results. Use --all to fetch all results."
|
|
305
|
+
)
|
|
306
|
+
pagination = None
|
|
307
|
+
if page.next_page_token and not stopped_mid_page:
|
|
308
|
+
pagination = {
|
|
309
|
+
"reminders": {
|
|
310
|
+
"nextCursor": page.next_page_token,
|
|
311
|
+
"prevCursor": None,
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return CommandOutput(
|
|
315
|
+
data={"reminders": results[:max_results]},
|
|
316
|
+
context=cmd_context,
|
|
317
|
+
pagination=pagination,
|
|
318
|
+
api_called=True,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if first_page and not all_pages and max_results is None:
|
|
322
|
+
pagination = (
|
|
323
|
+
{"reminders": {"nextCursor": page.next_page_token, "prevCursor": None}}
|
|
324
|
+
if page.next_page_token
|
|
325
|
+
else None
|
|
326
|
+
)
|
|
327
|
+
return CommandOutput(
|
|
328
|
+
data={"reminders": results},
|
|
329
|
+
context=cmd_context,
|
|
330
|
+
pagination=pagination,
|
|
331
|
+
api_called=True,
|
|
332
|
+
)
|
|
333
|
+
first_page = False
|
|
334
|
+
|
|
335
|
+
page_token = page.next_page_token
|
|
336
|
+
if not page_token:
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
return CommandOutput(
|
|
340
|
+
data={"reminders": results},
|
|
341
|
+
context=cmd_context,
|
|
342
|
+
pagination=None,
|
|
343
|
+
api_called=True,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
run_command(ctx, command="reminder ls", fn=fn)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@category("read")
|
|
350
|
+
@reminder_group.command(name="get", cls=RichCommand)
|
|
351
|
+
@click.argument("reminder_id", type=int)
|
|
352
|
+
@output_options
|
|
353
|
+
@click.pass_obj
|
|
354
|
+
def reminder_get(ctx: CLIContext, reminder_id: int) -> None:
|
|
355
|
+
"""Get a reminder by id."""
|
|
356
|
+
|
|
357
|
+
def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
|
|
358
|
+
client = ctx.get_client(warnings=warnings)
|
|
359
|
+
reminder = client.reminders.get(ReminderIdType(reminder_id))
|
|
360
|
+
|
|
361
|
+
cmd_context = CommandContext(
|
|
362
|
+
name="reminder get",
|
|
363
|
+
inputs={"reminderId": reminder_id},
|
|
364
|
+
modifiers={},
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return CommandOutput(
|
|
368
|
+
data={"reminder": _reminder_payload(reminder)},
|
|
369
|
+
context=cmd_context,
|
|
370
|
+
api_called=True,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
run_command(ctx, command="reminder get", fn=fn)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@category("write")
|
|
377
|
+
@reminder_group.command(name="create", cls=RichCommand)
|
|
378
|
+
@click.option("--owner-id", type=int, required=True, help="Owner id (required).")
|
|
379
|
+
@click.option(
|
|
380
|
+
"--type",
|
|
381
|
+
"reminder_type",
|
|
382
|
+
type=click.Choice(sorted(_REMINDER_TYPE_MAP.keys())),
|
|
383
|
+
required=True,
|
|
384
|
+
help="Reminder type (one-time, recurring).",
|
|
385
|
+
)
|
|
386
|
+
@click.option("--content", type=str, default=None, help="Reminder content.")
|
|
387
|
+
@click.option("--due-date", type=str, default=None, help="Due date (ISO-8601).")
|
|
388
|
+
@click.option(
|
|
389
|
+
"--reset-type",
|
|
390
|
+
type=click.Choice(sorted(_REMINDER_RESET_MAP.keys())),
|
|
391
|
+
default=None,
|
|
392
|
+
help="Reset type for recurring reminders.",
|
|
393
|
+
)
|
|
394
|
+
@click.option("--reminder-days", type=int, default=None, help="Days before due date to remind.")
|
|
395
|
+
@click.option("--person-id", type=int, default=None, help="Associate person id.")
|
|
396
|
+
@click.option("--company-id", type=int, default=None, help="Associate company id.")
|
|
397
|
+
@click.option("--opportunity-id", type=int, default=None, help="Associate opportunity id.")
|
|
398
|
+
@output_options
|
|
399
|
+
@click.pass_obj
|
|
400
|
+
def reminder_create(
|
|
401
|
+
ctx: CLIContext,
|
|
402
|
+
*,
|
|
403
|
+
owner_id: int,
|
|
404
|
+
reminder_type: str,
|
|
405
|
+
content: str | None,
|
|
406
|
+
due_date: str | None,
|
|
407
|
+
reset_type: str | None,
|
|
408
|
+
reminder_days: int | None,
|
|
409
|
+
person_id: int | None,
|
|
410
|
+
company_id: int | None,
|
|
411
|
+
opportunity_id: int | None,
|
|
412
|
+
) -> None:
|
|
413
|
+
"""Create a reminder."""
|
|
414
|
+
|
|
415
|
+
def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
|
|
416
|
+
_ = warnings
|
|
417
|
+
_validate_single_entity(person_id, company_id, opportunity_id)
|
|
418
|
+
|
|
419
|
+
parsed_type = parse_choice(reminder_type, _REMINDER_TYPE_MAP, label="reminder type")
|
|
420
|
+
if parsed_type is None:
|
|
421
|
+
raise CLIError("Missing reminder type.", error_type="usage_error", exit_code=2)
|
|
422
|
+
parsed_reset = parse_choice(reset_type, _REMINDER_RESET_MAP, label="reset type")
|
|
423
|
+
due_date_value = parse_iso_datetime(due_date, label="due-date") if due_date else None
|
|
424
|
+
|
|
425
|
+
client = ctx.get_client(warnings=warnings)
|
|
426
|
+
reminder = client.reminders.create(
|
|
427
|
+
ReminderCreate(
|
|
428
|
+
owner_id=UserId(owner_id),
|
|
429
|
+
type=parsed_type,
|
|
430
|
+
content=content,
|
|
431
|
+
due_date=due_date_value,
|
|
432
|
+
reset_type=parsed_reset,
|
|
433
|
+
reminder_days=reminder_days,
|
|
434
|
+
person_id=PersonId(person_id) if person_id is not None else None,
|
|
435
|
+
company_id=CompanyId(company_id) if company_id is not None else None,
|
|
436
|
+
opportunity_id=OpportunityId(opportunity_id)
|
|
437
|
+
if opportunity_id is not None
|
|
438
|
+
else None,
|
|
439
|
+
)
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Build CommandContext for reminder create
|
|
443
|
+
ctx_modifiers: dict[str, object] = {
|
|
444
|
+
"ownerId": owner_id,
|
|
445
|
+
"type": reminder_type,
|
|
446
|
+
}
|
|
447
|
+
if content:
|
|
448
|
+
ctx_modifiers["content"] = content
|
|
449
|
+
if due_date:
|
|
450
|
+
ctx_modifiers["dueDate"] = due_date
|
|
451
|
+
if reset_type:
|
|
452
|
+
ctx_modifiers["resetType"] = reset_type
|
|
453
|
+
if reminder_days is not None:
|
|
454
|
+
ctx_modifiers["reminderDays"] = reminder_days
|
|
455
|
+
if person_id is not None:
|
|
456
|
+
ctx_modifiers["personId"] = person_id
|
|
457
|
+
if company_id is not None:
|
|
458
|
+
ctx_modifiers["companyId"] = company_id
|
|
459
|
+
if opportunity_id is not None:
|
|
460
|
+
ctx_modifiers["opportunityId"] = opportunity_id
|
|
461
|
+
|
|
462
|
+
cmd_context = CommandContext(
|
|
463
|
+
name="reminder create",
|
|
464
|
+
inputs={},
|
|
465
|
+
modifiers=ctx_modifiers,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
return CommandOutput(
|
|
469
|
+
data={"reminder": _reminder_payload(reminder)},
|
|
470
|
+
context=cmd_context,
|
|
471
|
+
api_called=True,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
run_command(ctx, command="reminder create", fn=fn)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@category("write")
|
|
478
|
+
@reminder_group.command(name="update", cls=RichCommand)
|
|
479
|
+
@click.argument("reminder_id", type=int)
|
|
480
|
+
@click.option("--owner-id", type=int, default=None, help="Owner id.")
|
|
481
|
+
@click.option(
|
|
482
|
+
"--type",
|
|
483
|
+
"reminder_type",
|
|
484
|
+
type=click.Choice(sorted(_REMINDER_TYPE_MAP.keys())),
|
|
485
|
+
default=None,
|
|
486
|
+
help="Reminder type (one-time, recurring).",
|
|
487
|
+
)
|
|
488
|
+
@click.option("--content", type=str, default=None, help="Reminder content.")
|
|
489
|
+
@click.option("--due-date", type=str, default=None, help="Due date (ISO-8601).")
|
|
490
|
+
@click.option(
|
|
491
|
+
"--reset-type",
|
|
492
|
+
type=click.Choice(sorted(_REMINDER_RESET_MAP.keys())),
|
|
493
|
+
default=None,
|
|
494
|
+
help="Reset type for recurring reminders.",
|
|
495
|
+
)
|
|
496
|
+
@click.option("--reminder-days", type=int, default=None, help="Days before due date to remind.")
|
|
497
|
+
@click.option(
|
|
498
|
+
"--completed/--not-completed", "is_completed", default=None, help="Set completion status."
|
|
499
|
+
)
|
|
500
|
+
@output_options
|
|
501
|
+
@click.pass_obj
|
|
502
|
+
def reminder_update(
|
|
503
|
+
ctx: CLIContext,
|
|
504
|
+
reminder_id: int,
|
|
505
|
+
*,
|
|
506
|
+
owner_id: int | None,
|
|
507
|
+
reminder_type: str | None,
|
|
508
|
+
content: str | None,
|
|
509
|
+
due_date: str | None,
|
|
510
|
+
reset_type: str | None,
|
|
511
|
+
reminder_days: int | None,
|
|
512
|
+
is_completed: bool | None,
|
|
513
|
+
) -> None:
|
|
514
|
+
"""Update a reminder."""
|
|
515
|
+
|
|
516
|
+
def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
|
|
517
|
+
parsed_type = parse_choice(reminder_type, _REMINDER_TYPE_MAP, label="reminder type")
|
|
518
|
+
parsed_reset = parse_choice(reset_type, _REMINDER_RESET_MAP, label="reset type")
|
|
519
|
+
due_date_value = parse_iso_datetime(due_date, label="due-date") if due_date else None
|
|
520
|
+
|
|
521
|
+
client = ctx.get_client(warnings=warnings)
|
|
522
|
+
reminder = client.reminders.update(
|
|
523
|
+
ReminderIdType(reminder_id),
|
|
524
|
+
ReminderUpdate(
|
|
525
|
+
owner_id=UserId(owner_id) if owner_id is not None else None,
|
|
526
|
+
type=parsed_type,
|
|
527
|
+
content=content,
|
|
528
|
+
due_date=due_date_value,
|
|
529
|
+
reset_type=parsed_reset,
|
|
530
|
+
reminder_days=reminder_days,
|
|
531
|
+
is_completed=is_completed,
|
|
532
|
+
),
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
# Build CommandContext for reminder update
|
|
536
|
+
ctx_modifiers: dict[str, object] = {}
|
|
537
|
+
if owner_id is not None:
|
|
538
|
+
ctx_modifiers["ownerId"] = owner_id
|
|
539
|
+
if reminder_type:
|
|
540
|
+
ctx_modifiers["type"] = reminder_type
|
|
541
|
+
if content:
|
|
542
|
+
ctx_modifiers["content"] = content
|
|
543
|
+
if due_date:
|
|
544
|
+
ctx_modifiers["dueDate"] = due_date
|
|
545
|
+
if reset_type:
|
|
546
|
+
ctx_modifiers["resetType"] = reset_type
|
|
547
|
+
if reminder_days is not None:
|
|
548
|
+
ctx_modifiers["reminderDays"] = reminder_days
|
|
549
|
+
if is_completed is not None:
|
|
550
|
+
ctx_modifiers["completed"] = is_completed
|
|
551
|
+
|
|
552
|
+
cmd_context = CommandContext(
|
|
553
|
+
name="reminder update",
|
|
554
|
+
inputs={"reminderId": reminder_id},
|
|
555
|
+
modifiers=ctx_modifiers,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
return CommandOutput(
|
|
559
|
+
data={"reminder": _reminder_payload(reminder)},
|
|
560
|
+
context=cmd_context,
|
|
561
|
+
api_called=True,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
run_command(ctx, command="reminder update", fn=fn)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
@category("write")
|
|
568
|
+
@destructive
|
|
569
|
+
@reminder_group.command(name="delete", cls=RichCommand)
|
|
570
|
+
@click.argument("reminder_id", type=int)
|
|
571
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt.")
|
|
572
|
+
@output_options
|
|
573
|
+
@click.pass_obj
|
|
574
|
+
def reminder_delete(ctx: CLIContext, reminder_id: int, yes: bool) -> None:
|
|
575
|
+
"""Delete a reminder."""
|
|
576
|
+
if not yes:
|
|
577
|
+
click.confirm(f"Delete reminder {reminder_id}?", abort=True)
|
|
578
|
+
|
|
579
|
+
def fn(ctx: CLIContext, warnings: list[str]) -> CommandOutput:
|
|
580
|
+
client = ctx.get_client(warnings=warnings)
|
|
581
|
+
success = client.reminders.delete(ReminderIdType(reminder_id))
|
|
582
|
+
|
|
583
|
+
cmd_context = CommandContext(
|
|
584
|
+
name="reminder delete",
|
|
585
|
+
inputs={"reminderId": reminder_id},
|
|
586
|
+
modifiers={},
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
return CommandOutput(
|
|
590
|
+
data={"success": success},
|
|
591
|
+
context=cmd_context,
|
|
592
|
+
api_called=True,
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
run_command(ctx, command="reminder delete", fn=fn)
|